为什么Rust可执行文件如此庞大?

Bit*_*ler 128 rust rust-cargo

刚刚找到Rust并阅读了文档的前两章,我发现他们定义语言的方法和方式特别有趣.所以我决定让我的手指湿润,开始使用Hello world ...

我在Windows 7 x64上这样做了,顺便说一下.

fn main() {
    println!("Hello, world!");
}
Run Code Online (Sandbox Code Playgroud)

发行cargo build并查看结果targets\debug我发现所得.exe的存在3MB.经过一些搜索(很难找到货物命令行标志的文档......)我找到了--release选项并创建了发布版本.令我惊讶的是,.exe大小只变小了一小部分:2.99MB而不是3MB.

因此,承认我是Rust及其生态系统的新手,我的期望是系统编程语言会产生紧凑的东西.

任何人都可以详细说明Rust正在编译的内容,如何通过3个线程程序生成如此巨大的图像?它是否正在编译为虚拟机?我错过了一个strip命令(在发布版本中调试信息吗?)?还有什么可能让我们了解正在发生的事情?

ASt*_*her 114

Rust使用静态链接来编译程序,这意味着即使是最简单的Hello world!程序所需的所有库也将被编译到您的可执行文件中.这还包括Rust运行时.

要强制Rust动态链接程序,请使用命令行参数-C prefer-dynamic; 这将导致文件小得多,也需要Rust库(包括其运行时)在运行时可用于您的程序.这基本上意味着你将需要为他们提供如果计算机没有他们,占用了更多的比你原来的静态链接程序占用空间.

为了便于携带,我建议你以你曾经做过的方式静态链接Rust库和运行时,如果你要将程序分发给其他人.

  • 我不认为静态链接解释了巨大的HELLO-WORLD.它不应该只链接到实际使用的库的部分,而HELLO-WORLD几乎什么都不使用? (10认同)
  • BitTickler`carin rustc [--debug or --release] - -C prefer-dynamic` (8认同)
  • @Nulik:是的,默认情况下,但那是因为 Rust 默认为静态构建(包括所有依赖项,包括运行时),而 Go 动态链接其运行时。在我的 CentOS 7 系统上,Go 的 `helloworld` 编译为 ~76K,但除了标准内容之外,它还需要对 `libgo.so` 的运行时动态依赖,该依赖项超过 47M。默认的 Rust `helloworld` (使用 `cargo new` 制作的)没有任何独特的动态依赖关系,在 1.6M 可执行文件中保存除了基本 C 运行时内容之外的所有内容;通过调整(优化大小、使用 LTO、恐慌中止),它下降到 0.6M。 (6认同)
  • `-C Preferred-dynamic` 选项将发布版本(仅启用大小优化;它不会让我使用 LTO 或在恐慌时中止)降至 8.8K,尽管有一个新的 4.7M 动态依赖项。所以比较起来,Rust 更小;它的大小是动态链接的十分之一,依赖于大小也是十分之一的运行时。 (5认同)
  • @ user2225104对货物不确定,但根据[关于GitHub的这个错误报告](https://github.com/rust-lang/cargo/issues/60),遗憾的是这还不可能. (4认同)
  • 否决,因为这是错误的:静态链接并不一定意味着极其肥胖的二进制文件。不需要的符号可以被删除。例如,Visual C++ 2017 x64 对于带有 /EHsc /O2 /MT 的 `int main(){std::cout<<"hello world\n";}` 产生 219 kiB。或者 Mingw-w64 g++ 10.2 914 kiB,传递 -s -O2 -static。这是 C++ Iostream 的情况,它们本身相当臃肿,需要携带格式化状态、异常、区域设置等等。替换为 `std::printf` 我得到 117 kiB (cl) 或 41 kiB (g++)。Rust 可以改进这 41 kiB,因为 Rust I/O 没有语言环境,但情况更糟。 (4认同)
  • Fwiw,在我的机器上,提问者的 Rust 示例默认生成 4092 kiB (x86_64-pc-windows-gnu)。将 lto = true 添加到 Cargo.toml 会产生 1406 kiB。添加panic = "abort"、opt-level = "z"、codegen-units = 1 使得大小为1264 kiB。每晚设置 strip = "symbols" 将其减少到 220 kiB。这个数字对于 C++ 来说可能是合理的,但在我看来,Rust 没有理由生成如此不必要的大可执行文件。据我所知,与 C++ 相比,Rust 的隐含开销要少得多。如果人们真正关心的话,它会做得更好。 (4认同)
  • ``prefer-dynamic``似乎对我不起作用.错字? (3认同)
  • @daboross非常感谢你.我一直在跟踪[这个相关的RFC](https://github.com/rust-lang/rfcs/issues/600).由于Rust还针对系统编程,所以真的很可惜. (3认同)
  • 但是,一旦系统上有两个以上的rust可执行文件,动态链接将开始为您节省空间。 (2认同)

aij*_*aij 54

我没有任何Windows系统可以试用,但在Linux上,静态编译的Rust hello world实际上比同等C小.如果你看到大小的巨大差异,可能是因为你链接了Rust可执行文件静态地和C一动态.

使用动态链接,您还需要考虑所有动态库的大小,而不仅仅是可执行文件.

因此,如果你想比较苹果和苹果,你需要确保两者都是动态的或两者都是静态的.不同的编译器将具有不同的默认值,因此您不能仅依靠编译器默认值来生成相同的结果.

如果你有兴趣,这是我的结果:

-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)(建于2015-04-03)-static编译的,两者都有默认选项,而且用于gcc和-C prefer-dynamicfor rustc.

我有两个版本的C hello世界,因为我认为使用puts()可能会链接更少的编译单元.

如果你想尝试在Windows上复制它,这里是我使用的来源:

printf.c:

#include <stdio.h>
int main() {
  printf("Hello, world!\n");
}
Run Code Online (Sandbox Code Playgroud)

puts.c:

#include <stdio.h>
int main() {
  puts("Hello, world!");
}
Run Code Online (Sandbox Code Playgroud)

rust.rs

fn main() {
    println!("Hello, world!");
}
Run Code Online (Sandbox Code Playgroud)

此外,请记住,不同数量的调试信息或不同的优化级别也会产生影响.但我希望如果你看到一个巨大的差异,那是由于静态与动态链接.

  • gcc足够聪明,可以完全执行printf - >置换替换本身,这就是结果相同的原因. (22认同)
  • 从2018年开始,如果您想进行公平的比较,请记住要``剥离''可执行文件,因为您好,我系统上的Rust可执行文件高达5.3MB,但是当您删除所有调试符号时,降至不到10%这样。 (2认同)
  • @MattiVikkunen:2020 年仍然如此;自然大小似乎更小(远未达到 5.3M),但符号与代码的比例仍然相当极端。调试版本是 CentOS 7 上 Rust 1.34.0 的纯默认选项,使用“strip -s”进行剥离,从 1.6M 下降到 190K。发布版本(默认加上 `opt-level='s'`、`lto = true` 和 `panic = 'abort'` 以最小化大小)从 623K 下降到 158K。 (2认同)

小智 24

使用Cargo进行编译时,您可以使用动态链接:

cargo rustc --release -- -C prefer-dynamic
Run Code Online (Sandbox Code Playgroud)

这将大大减小二进制文件的大小,因为它现在是动态链接的.

至少在Linux上,您还可以使用以下strip命令去除符号的二进制:

strip target/release/<binary>
Run Code Online (Sandbox Code Playgroud)

这大约会使大多数二进制文件的大小减半.

  • 只是一些统计数据,hello world的默认发布版本(linux x86_64).3.5 M,偏好动态8904 B,剥离6392 B. (7认同)
  • 应该添加一条注释,说明它不利于分发。 (7认同)

pho*_*nix 13

有关减少Rust二进制文件大小的所有方法的概述,请参见min-sized-rust存储库。

当前减少二进制文件大小的高级步骤是:

  1. 使用Rust 1.32.0或更高版本(jemalloc默认情况下不包括)
  2. 将以下内容添加到 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
Run Code Online (Sandbox Code Playgroud)
  1. 使用发布模式构建 cargo build --release
  2. strip在生成的二进制文件上运行。

使用nightlyRust 可以完成更多工作,但是min-sized-rust由于使用了不稳定的功能,信息会随着时间的推移而变化,因此我会保留这些信息。

您也可以使用#![no_std]删除Rust的libstd。有关min-sized-rust详细信息,请参见。

  • 哇,这将我的可执行文件从 50MB 缩小到 6MB!没想到进步这么大 (21认同)
  • 请注意,这些设置会对性能产生影响,因此请确保这对您的用例来说不是问题。 (13认同)
  • 使用 `RUSTFLAGS='-C strip=symbols' Cargo build --release` 进行编译,以去除具有稳定 rustc 标志的二进制文件 (2认同)

Mii*_*iao 5

#![no_main]
#![no_std]

#[link(name = "msvcrt", kind = "dylib")]
extern {
    fn puts(ptr: *const u8); // i8 or u8 doesn't matter in this case
}

#[no_mangle]
unsafe extern fn main() {
    puts("Hello, World!\0".as_ptr());
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}
Run Code Online (Sandbox Code Playgroud)

与下一个配置文件

[profile.release]
debug = false
strip = true
opt-level = 'z'
codegen-units = 1
lto = true
panic = 'abort'
Run Code Online (Sandbox Code Playgroud)

给出 9 kb -r,而 C

#include <stdio.h>

main() {
    puts("Hello, World!");
}
Run Code Online (Sandbox Code Playgroud)

使用 GCC 给出 48 kb,-Os使用 TCC 给出 2 kb。非常令人印象深刻,不是吗?