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

Why are Rust executables so huge?

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

我是在Windows 7 x64,btw上执行的。

1
2
3
fn main() {
    println!("Hello, world!");
}

发出cargo build并查看targets\debug中的结果,我发现生成的.exe为3MB。经过一些搜索(很难找到货运命令行标志的文档...),我找到了--release选项并创建了发布版本。令我惊讶的是,.exe的大小仅变小了一点:2.99MB而不是3MB。

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

谁能详细说明Rust编译的内容,如何通过3线性程序生成如此大的图像?它可以编译到虚拟机吗?我是否错过了一个剥离命令(在发行版内部调试信息?)?还有什么可能让您了解正在发生的事情吗?


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

要强制Rust动态链接程序,请使用命令行参数-C prefer-dynamic;这将导致文件大小更小,但同时要求Rust库(包括其运行时)在运行时可用于您的程序。
从本质上讲,这意味着如果计算机没有它们,则需要提供它们,与原始静态链接程序所占用的空间相比,它们会占用更多的空间。

为了可移植性,如果您要将程序分发给其他人,我建议您以您一直使用的方式静态链接Rust库和运行时。


我没有任何Windows系统可以尝试,但是在Linux上,静态编译的Rust hello世界实际上小于等效的C。如果您看到大小上的巨大差异,则可能是因为您链接了Rust可执行文件静态和C动态。

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

因此,如果您想将一个苹果与另一个苹果进行比较,则需要确保两者都是动态的,或者两者都是静态的。不同的编译器将具有不同的默认值,因此您不能仅仅依靠编译器的默认值来产生相同的结果。

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

1
2
3
4
5
6
7
8
9
-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-dynamic表示rustc。

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

如果要尝试在Windows上重现它,请使用以下来源:

printf.c:

1
2
3
4
5
#include <stdio.h>
int main() {
  printf("Hello, world!
");
}

puts.c:

1
2
3
4
#include <stdio.h>
int main() {
  puts("Hello, world!");
}

rust.rs

1
2
3
fn main() {
    println!("Hello, world!");
}

另外,请记住,不同数量的调试信息或不同的优化级别也会有所不同。但是我希望,如果您看到巨大的差异,那是由于静态链接与动态链接所致。


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

1
cargo rustc --release -- -C prefer-dynamic

由于它现在是动态链接的,因此将大大减少二进制文件的大小。

至少在Linux上,您还可以使用strip命令剥离符号的二进制文件:

1
strip target/release/<binary>

这将使大多数二进制文件的大小大约减半。


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

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

  • 使用Rust 1.32.0或更高版本(默认情况下不包含jemalloc)
  • 将以下内容添加到Cargo.toml
  • 1
    2
    3
    4
    5
    [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
  • 使用cargo build --release在发布模式下构建
  • 在生成的二进制文件上运行strip
  • 使用nightly Rust可以完成更多工作,但是由于使用了不稳定的功能,信息会随着时间而变化,因此我会将其保留在min-sized-rust中。

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


    这是功能,不是错误!

    您可以指定程序中使用的库版本(在项目的关联Cargo.toml文件中)(甚至是隐式版本),以确保库版本兼容。另一方面,这要求将特定库静态链接到可执行文件,从而生成较大的运行时映像。

    嘿,现在不再是1978年-许多人的计算机中的RAM超过2 MB :-)