为什么 Rust 可执行文件如此庞大?
Why are Rust executables so huge?
刚刚找到 Rust 并阅读了文档的前两章,我发现他们定义语言的方法和方式特别有趣。所以我决定亲自动手,开始使用 Hello world...
我在 Windows 7 x64 上这样做了,顺便说一句。
fn main() {
println!("Hello, world!");
}
发出 cargo build
并查看 targets\debug
中的结果,我发现结果 .exe
为 3MB。经过一些搜索(cargo 命令行标志的文档很难找到......)我找到了 --release
选项并创建了发布版本。令我惊讶的是,.exe 的大小只变小了一点点:2.99MB 而不是 3MB。
所以,承认我是 Rust 及其生态系统的新手,我的期望是系统编程语言会产生紧凑的东西。
任何人都可以详细说明 Rust 正在编译什么,它怎么可能从 3 行程序生成如此巨大的图像?它是编译到虚拟机吗?是否有我错过的 strip 命令(发布版本中的调试信息?)?还有什么可以让我们理解正在发生的事情吗?
Rust 使用静态 linking 来编译它的程序,这意味着即使是最简单的 Hello world!
程序所需的所有库都将被编译到您的可执行文件中。这也包括 Rust 运行时。
要强制 Rust 动态地 link 程序,请使用命令行参数 -C prefer-dynamic
;这将导致文件大小小得多 但是 还需要 Rust 库(包括其运行时)在运行时可供您的程序使用。
这实质上意味着如果计算机没有它们,您将需要提供它们,占用 更多 space 比原始静态 linked 程序占用。
为了可移植性,如果您要将程序分发给其他人,我建议您静态地 link Rust 库和运行时,就像您一直在做的那样。
我没有任何 Windows 系统可以尝试,但是在 Linux 上,静态编译的 Rust hello world 实际上比等效的 C 小。如果你看到一个巨大的差异在大小上,这可能是因为您正在 link 静态地运行 Rust 可执行文件而动态地运行 C 可执行文件。
使用动态 linking,您还需要考虑所有动态库的大小,而不仅仅是可执行文件。
因此,如果您想将苹果与苹果进行比较,您需要确保两者都是动态的或都是静态的。不同的编译器会有不同的默认值,所以你不能仅仅依靠编译器的默认值来产生相同的结果。
如果您有兴趣,这是我的结果:
-rw-r--r-- 1 aij aij 63 Apr 5 14:26 printf.c
-rwxr-xr-x 1 aij aij 6696 Apr 5 14:27 printf.dyn
-rwxr-xr-x 1 aij aij 829344 Apr 5 14:27 printf.static
-rw-r--r-- 1 aij aij 59 Apr 5 14:26 puts.c
-rwxr-xr-x 1 aij aij 6696 Apr 5 14:27 puts.dyn
-rwxr-xr-x 1 aij aij 829344 Apr 5 14:27 puts.static
-rwxr-xr-x 1 aij aij 8712 Apr 5 14:28 rust.dyn
-rw-r--r-- 1 aij aij 46 Apr 5 14:09 rust.rs
-rwxr-xr-x 1 aij aij 661496 Apr 5 14:28 rust.static
这些是用 gcc (Debian 4.9.2-10) 4.9.2 和 rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (built 2015-04-03) 编译的,都有默认选项和-static
用于 gcc,-C prefer-dynamic
用于 rustc。
我有两个版本的 C hello world,因为我认为使用 puts()
可能 link 在更少的编译单元中。
如果您想尝试在 Windows 上复制它,这里是我使用的来源:
printf.c:
#include <stdio.h>
int main() {
printf("Hello, world!\n");
}
puts.c:
#include <stdio.h>
int main() {
puts("Hello, world!");
}
rust.rs
fn main() {
println!("Hello, world!");
}
此外,请记住,不同数量的调试信息或不同的优化级别也会有所不同。但我希望如果您看到巨大差异,那是由于静态与动态 linking.
使用 Cargo 编译时,可以使用动态链接:
cargo rustc --release -- -C prefer-dynamic
这将大大减少二进制文件的大小,因为它现在是动态链接的。
在 Linux 上,至少,您还可以使用 strip
命令去除二进制符号:
strip target/release/<binary>
这将使大多数二进制文件的大小大约减半。
这是一个功能,不是错误!
您可以指定程序中使用的库版本(在project's associated Cargo.toml file中)(即使是隐含的)以确保库版本兼容性。另一方面,这要求将特定库静态链接到可执行文件,生成大型 运行 时间图像。
嘿,现在已经不是 1978 年了 - 许多人的计算机内存都超过 2 MB :-)
默认情况下,Rust 编译器会优化执行速度、编译速度和调试的简易性(例如,通过包含符号),而不是最小二进制大小。
有关减小 Rust 二进制文件大小的所有方法的概述,请参阅 min-sized-rust
存储库。
当前减少二进制大小的高级步骤是:
- 使用 Rust 1.32.0 或更新版本(默认情况下不包括
jemalloc
)
- 将以下内容添加到
Cargo.toml
:
[profile.release]
opt-level = 'z' # Optimize for size.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = 'abort' # Abort on panic
strip = true # Strip symbols from binary*
*strip = true
requires Rust 1.59+. On older Rust versions, run strip
manually on the resulting binary.
- 使用
cargo build --release
在发布模式下构建
使用 nightly
Rust 可以完成更多工作,但我会在 min-sized-rust
中保留该信息,因为由于使用不稳定的功能,它会随着时间的推移而变化。
您还可以使用 #![no_std]
删除 Rust 的 libstd
。有关详细信息,请参阅 min-sized-rust
。
安装rust nightly -
rustup toolchain install nightly
、rustup default nightly
现在,在项目的 所有 Cargo.toml 文件中进行这些更改。
在 Cargo.toml
顶部的 [package]
之前添加 cargo-features = ["strip"]
在底部,或[dependencies]
和[package]
之间添加,
[profile.release]
# strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size.
lto = true # Enable link time optimization
codegen-units = 1 # Reduce parallel code generation units
现在用 RUSTFLAGS='-C link-arg=-s' cargo build --release
构建
我发现这些链接很有用 - https://collabora.com/news-and-blog/blog/2020/04/28/reducing-size-rust-gstreamer-plugin/ and https://github.com/johnthagen/min-sized-rust and https://arusahni.net/blog/2020/03/optimizing-rust-binary-size.html
刚刚找到 Rust 并阅读了文档的前两章,我发现他们定义语言的方法和方式特别有趣。所以我决定亲自动手,开始使用 Hello world...
我在 Windows 7 x64 上这样做了,顺便说一句。
fn main() {
println!("Hello, world!");
}
发出 cargo build
并查看 targets\debug
中的结果,我发现结果 .exe
为 3MB。经过一些搜索(cargo 命令行标志的文档很难找到......)我找到了 --release
选项并创建了发布版本。令我惊讶的是,.exe 的大小只变小了一点点:2.99MB 而不是 3MB。
所以,承认我是 Rust 及其生态系统的新手,我的期望是系统编程语言会产生紧凑的东西。
任何人都可以详细说明 Rust 正在编译什么,它怎么可能从 3 行程序生成如此巨大的图像?它是编译到虚拟机吗?是否有我错过的 strip 命令(发布版本中的调试信息?)?还有什么可以让我们理解正在发生的事情吗?
Rust 使用静态 linking 来编译它的程序,这意味着即使是最简单的 Hello world!
程序所需的所有库都将被编译到您的可执行文件中。这也包括 Rust 运行时。
要强制 Rust 动态地 link 程序,请使用命令行参数 -C prefer-dynamic
;这将导致文件大小小得多 但是 还需要 Rust 库(包括其运行时)在运行时可供您的程序使用。
这实质上意味着如果计算机没有它们,您将需要提供它们,占用 更多 space 比原始静态 linked 程序占用。
为了可移植性,如果您要将程序分发给其他人,我建议您静态地 link Rust 库和运行时,就像您一直在做的那样。
我没有任何 Windows 系统可以尝试,但是在 Linux 上,静态编译的 Rust hello world 实际上比等效的 C 小。如果你看到一个巨大的差异在大小上,这可能是因为您正在 link 静态地运行 Rust 可执行文件而动态地运行 C 可执行文件。
使用动态 linking,您还需要考虑所有动态库的大小,而不仅仅是可执行文件。
因此,如果您想将苹果与苹果进行比较,您需要确保两者都是动态的或都是静态的。不同的编译器会有不同的默认值,所以你不能仅仅依靠编译器的默认值来产生相同的结果。
如果您有兴趣,这是我的结果:
-rw-r--r-- 1 aij aij 63 Apr 5 14:26 printf.c -rwxr-xr-x 1 aij aij 6696 Apr 5 14:27 printf.dyn -rwxr-xr-x 1 aij aij 829344 Apr 5 14:27 printf.static -rw-r--r-- 1 aij aij 59 Apr 5 14:26 puts.c -rwxr-xr-x 1 aij aij 6696 Apr 5 14:27 puts.dyn -rwxr-xr-x 1 aij aij 829344 Apr 5 14:27 puts.static -rwxr-xr-x 1 aij aij 8712 Apr 5 14:28 rust.dyn -rw-r--r-- 1 aij aij 46 Apr 5 14:09 rust.rs -rwxr-xr-x 1 aij aij 661496 Apr 5 14:28 rust.static
这些是用 gcc (Debian 4.9.2-10) 4.9.2 和 rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (built 2015-04-03) 编译的,都有默认选项和-static
用于 gcc,-C prefer-dynamic
用于 rustc。
我有两个版本的 C hello world,因为我认为使用 puts()
可能 link 在更少的编译单元中。
如果您想尝试在 Windows 上复制它,这里是我使用的来源:
printf.c:
#include <stdio.h>
int main() {
printf("Hello, world!\n");
}
puts.c:
#include <stdio.h>
int main() {
puts("Hello, world!");
}
rust.rs
fn main() {
println!("Hello, world!");
}
此外,请记住,不同数量的调试信息或不同的优化级别也会有所不同。但我希望如果您看到巨大差异,那是由于静态与动态 linking.
使用 Cargo 编译时,可以使用动态链接:
cargo rustc --release -- -C prefer-dynamic
这将大大减少二进制文件的大小,因为它现在是动态链接的。
在 Linux 上,至少,您还可以使用 strip
命令去除二进制符号:
strip target/release/<binary>
这将使大多数二进制文件的大小大约减半。
这是一个功能,不是错误!
您可以指定程序中使用的库版本(在project's associated Cargo.toml file中)(即使是隐含的)以确保库版本兼容性。另一方面,这要求将特定库静态链接到可执行文件,生成大型 运行 时间图像。
嘿,现在已经不是 1978 年了 - 许多人的计算机内存都超过 2 MB :-)
默认情况下,Rust 编译器会优化执行速度、编译速度和调试的简易性(例如,通过包含符号),而不是最小二进制大小。
有关减小 Rust 二进制文件大小的所有方法的概述,请参阅 min-sized-rust
存储库。
当前减少二进制大小的高级步骤是:
- 使用 Rust 1.32.0 或更新版本(默认情况下不包括
jemalloc
) - 将以下内容添加到
Cargo.toml
:
[profile.release]
opt-level = 'z' # Optimize for size.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = 'abort' # Abort on panic
strip = true # Strip symbols from binary*
*
strip = true
requires Rust 1.59+. On older Rust versions, runstrip
manually on the resulting binary.
- 使用
cargo build --release
在发布模式下构建
使用 nightly
Rust 可以完成更多工作,但我会在 min-sized-rust
中保留该信息,因为由于使用不稳定的功能,它会随着时间的推移而变化。
您还可以使用 #![no_std]
删除 Rust 的 libstd
。有关详细信息,请参阅 min-sized-rust
。
安装rust nightly -
rustup toolchain install nightly
、rustup default nightly
现在,在项目的 所有 Cargo.toml 文件中进行这些更改。
在 Cargo.toml
顶部的[package]
之前添加 cargo-features = ["strip"]
在底部,或[dependencies]
和[package]
之间添加,
[profile.release]
# strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size.
lto = true # Enable link time optimization
codegen-units = 1 # Reduce parallel code generation units
现在用 RUSTFLAGS='-C link-arg=-s' cargo build --release
我发现这些链接很有用 - https://collabora.com/news-and-blog/blog/2020/04/28/reducing-size-rust-gstreamer-plugin/ and https://github.com/johnthagen/min-sized-rust and https://arusahni.net/blog/2020/03/optimizing-rust-binary-size.html