简体   繁体   English

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

[英]Why are Rust executables so huge?

Just having found Rust and having read the first two chapters of the documentation, I find the approach and the way they defined the language particularly interesting.刚刚找到 Rust 并阅读了文档的前两章,我发现他们定义语言的方法和方式特别有趣。 So I decided to get my fingers wet and started out with "Hello, world.".所以我决定亲自动手,从“Hello, world.”开始。

I did so on Windows 7 x64, btw.顺便说一句,我是在 Windows 7 x64 上这样做的。

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

Issuing cargo build and looking at the result in targets\debug I found the resulting .exe being 3MB.发布cargo build并在targets\debug中查看结果,我发现生成的.exe为 3MB。 After some searching (documentation of cargo command line flags is hard to find...) I found the --release option and created the release build.经过一些搜索(cargo 命令行标志的文档很难找到......)我找到了--release选项并创建了发布版本。 To my surprise, the.exe size has only become smaller by an insignificant amount: 2.99MB instead of 3MB.令我惊讶的是,.exe 的大小只变小了一个微不足道的量:2.99MB 而不是 3MB。

So, confessing I am a newbie to Rust and its ecosystem, my expectation would have been that a systems programming language would produce something compact.因此,承认我是 Rust 及其生态系统的新手,我的期望是系统编程语言会产生一些紧凑的东西。

Can anyone elaborate on what Rust is compiling to, how it can be possible it produces such huge images from a 3-line program?任何人都可以详细说明 Rust 正在编译什么,它怎么可能从 3 行程序生成如此巨大的图像? Is it compiling to a virtual machine?它是编译到虚拟机吗? Is there a strip command I missed (debug info inside the release build?)?是否有我错过的 strip 命令(发布版本中的调试信息?)? Anything else which might allow to understand what is going on?还有什么可以让我们理解正在发生的事情吗?

Rust uses static linking to compile its programs, meaning that all libraries required by even the simplest Hello world! Rust 使用静态链接来编译它的程序,这意味着即使是最简单的Hello world! program will be compiled into your executable.程序将被编译成您的可执行文件。 This also includes the Rust runtime.这也包括 Rust 运行时。

To force Rust to dynamically link programs, use the command-line arguments -C prefer-dynamic ;要强制 Rust 动态链接程序,请使用命令行参数-C prefer-dynamic this will result in a much smaller file size but will also require the Rust libraries (including its runtime) to be available to your program at runtime.这将导致文件大小小得多,也需要 Rust 库(包括其运行时)在运行时可供您的程序使用。 This essentially means you will need to provide them if the computer does not have them, taking up more space than your original statically linked program takes up.这基本上意味着你将需要为他们提供如果计算机没有他们,占用了比你原来的静态链接程序占用更多的空间。

For portability I'd recommend you statically link the Rust libraries and runtime in the way you have been doing if you were to ever distribute your programs to others.为了可移植性,如果您要将程序分发给其他人,我建议您按照您一直在做的方式静态链接 Rust 库和运行时。

I don't have any Windows systems to try on, but on Linux, a statically compiled Rust hello world is actually smaller than the equivalent C. If you are seeing a huge difference in size, it is probably because you are linking the Rust executable statically and the C one dynamically.我没有任何 Windows 系统可以尝试,但在 Linux 上,静态编译的 Rust hello world 实际上比等效的 C 小。如果您看到大小差异很大,那可能是因为您正在链接 Rust 可执行文件静态和 C 一个动态。

With dynamic linking, you need to take the size of all the dynamic libraries into account too, not just the executable.使用动态链接,您还需要考虑所有动态库的大小,而不仅仅是可执行文件。

So, if you want to compare apples to apples, you need to make sure either both are dynamic or both are static.因此,如果您想将苹果与苹果进行比较,您需要确保两者都是动态的,或者都是静态的。 Different compilers will have different defaults, so you can't just rely on the compiler defaults to produce the same result.不同的编译器会有不同的默认值,所以你不能仅仅依赖编译器的默认值来产生相同的结果。

If you're interested, here are my results:如果你有兴趣,这是我的结果:

-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

These were compiled with gcc (Debian 4.9.2-10) 4.9.2 and rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (built 2015-04-03), both with default options and with -static for gcc and -C prefer-dynamic for rustc.这些是用 gcc (Debian 4.9.2-10) 4.9.2 和 rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (built 2015-04-03) 编译的,都有默认选项和-static for gcc和-C prefer-dynamic rustc -C prefer-dynamic

I had two versions of the C hello world because I thought using puts() might link in fewer compilation units.我有两个版本的 C hello world,因为我认为使用puts()可能会链接更少的编译单元。

If you want to try reproducing it on Windows, here are the sources I used:如果你想尝试在 Windows 上重现它,这里是我使用的来源:

printf.c: printf.c:

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

puts.c: puts.c:

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

rust.rs rust.rs

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

Also, keep in mind that different amounts of debugging information, or different optimization levels would also make a difference.此外,请记住,不同数量的调试信息或不同的优化级别也会有所不同。 But I expect if you are seeing a huge difference it is due to static vs. dynamic linking.但是我希望如果您看到巨大的差异,那是由于静态链接与动态链接。

For an overview of all of the ways to reduce the size of a Rust binary, see the min-sized-rust repository.有关减小 Rust 二进制文件大小的所有方法的概述,请参阅min-sized-rust存储库。

The current high level steps to reduce binary size are:当前减少二进制大小的高级步骤是:

  1. Use Rust 1.32.0 or newer (which doesn't include jemalloc by default)使用 Rust 1.32.0 或更新版本(默认情况下不包括jemalloc
  2. Add the following to Cargo.toml将以下内容添加到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
  1. Build in release mode using cargo build --release使用cargo build --release在发布模式下cargo build --release
  2. Run strip on the resulting binary.在生成的二进制文件上运行strip

There is more that can be done using nightly Rust, but I'll leave that information in min-sized-rust as it changes over time due to the use of unstable features.使用nightly Rust 可以做更多事情,但我会将这些信息留在min-sized-rust因为它会随着时间的推移而变化,因为使用了不稳定的功能。

You can also use #![no_std] to remove Rust's libstd .您还可以使用#![no_std]删除 Rust 的libstd See min-sized-rust for details.有关详细信息,请参阅min-sized-rust

When compiling with Cargo, you can use dynamic linking:使用 Cargo 编译时,可以使用动态链接:

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

This will dramatically reduce the size of the binary, as it is now dynamically linked.这将大大减少二进制文件的大小,因为它现在是动态链接的。

On Linux, at least, you can also strip the binary of symbols using the strip command:至少在 Linux 上,您还可以使用strip命令剥离二进制符号:

strip target/release/<binary>

This will approximately halve the size of most binaries.这将使大多数二进制文件的大小大约减半。

Install rust nightly - rustup toolchain install nightly , rustup default nightly 每晚安装rust - rustup toolchain install nightlyrustup default nightly rustup toolchain install nightly

Now, make these changes in all the Cargo.toml files in your project.现在,在项目中的所有Cargo.toml 文件中进行这些更改。

Add cargo-features = ["strip"] before [package] at the top of the Cargo.toml在Cargo.toml顶部的[package]之前添加cargo-features = ["strip"]

At the bottom, or between [dependencies] and [package] add,在底部,或者在[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

Now build with RUSTFLAGS='-C link-arg=-s' cargo build --release现在使用RUSTFLAGS='-C link-arg=-s' cargo build --release

I found these links useful - 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我发现这些链接很有用 - https://collabora.com/news-and-blog/blog/2020/04/28/reducing-size-rust-gstreamer-plugin/https://github.com/johnthagen/min -size-rusthttps://arusahni.net/blog/2020/03/optimizing-rust-binary-size.html

One of the reasons why the executables may be large is due to how the Rust compiler handles generics.可执行文件可能很大的原因之一是 Rust 编译器处理泛型的方式。 Rust does NOT use polymorphism, but rather monomorphization . Rust 不使用多态性,而是使用单态化 What this means is that the compiler creates a separate copy of the code for each generic function for each concrete type (as needed).这意味着编译器为每个具体类型的每个泛型函数创建一个单独的代码副本(根据需要)。The dev guide outlines it here .开发指南在这里概述了它

eg if I use a generic type like Vec and in my code I have several instances like Vec<i32> , Vec<bool> , and Vec<String> , then the compiler will make 3 separate copies of generated code for the aforementioned generics.例如,如果我使用像Vec这样的泛型类型,并且在我的代码中有多个实例,如Vec<i32>Vec<bool>Vec<String> ,那么编译器将为上述泛型生成生成的代码的 3 个单独副本。 One for Vec<i32> , one for Vec<bool> , etc...一个用于Vec<i32> ,一个用于Vec<bool> ,等等......

One of the drawbacks to this approach is that compile time is increased and binaries are larger.这种方法的缺点之一是编译时间增加并且二进制文件更大。 On the plus side, the compiled code should run faster since nothing needs to be inferred at runtime and each concrete type has (should have?) an optimized implementation for that concrete type.从好的方面来说,编译后的代码应该运行得更快,因为在运行时不需要推断任何东西,并且每个具体类型都有(应该有?)针对该具体类型的优化实现。

Just having found Rust and having read the first two chapters of the documentation, I find the approach and the way they defined the language particularly interesting.刚刚找到 Rust 并阅读了文档的前两章,我发现他们定义语言的方法和方式特别有趣。 So I decided to get my fingers wet and started out with Hello world...所以我决定弄湿我的手指并开始使用 Hello world ...

I did so on Windows 7 x64, btw.我是在 Windows 7 x64 上这样做的,顺便说一句。

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

Issuing cargo build and looking at the result in targets\\debug I found the resulting .exe being 3MB.发出cargo build并查看targets\\debug中的结果,我发现生成的.exe为 3MB。 After some searching (documentation of cargo command line flags is hard to find...) I found --release option and created the release build.经过一番搜索(很难找到有关货物命令行标志的文档...),我找到了--release选项并创建了发布版本。 To my surprise, the .exe size has only become smaller by an insignificant amount: 2.99MB instead of 3MB.令我惊讶的是,.exe 的大小只变小了一点点:2.99MB 而不是 3MB。

So, confessing I am a newbie to Rust and its ecosystem, my expectation would have been that a Systems Programming language would produce something compact.所以,承认我是 Rust 及其生态系统的新手,我的期望是系统编程语言会产生一些紧凑的东西。

Can anyone elaborate on what Rust is compiling to, how it can be possible it produces such huge images from a 3 liner program?任何人都可以详细说明 Rust 正在编译的内容,它如何从 3 行程序生成如此巨大的图像? Is it compiling to a virtual machine?是编译成虚拟机吗? Is there a strip command I missed (debug info inside the release build?)?是否有我错过的 strip 命令(发布版本中的调试信息?)? Anything else which might allow to understand what is going on?还有什么可能让您了解正在发生的事情吗?

This is a feature, not a bug!这是一个功能,而不是一个错误!

You can specify the library versions (in the project's associated Cargo.toml file ) used in the program (even the implicit ones) to ensure library version compatibility.您可以指定程序中使用的库版本(在项目关联的 Cargo.toml 文件中)(甚至是隐式版本)以确保库版本兼容性。 This, on the other hand, requires that the specific library be statically linked to the executable, generating large run-time images.另一方面,这需要将特定库静态链接到可执行文件,从而生成大型运行时映像。

Hey, it's not 1978 any more - many people have more than 2 MB RAM in their computers :-)嘿,现在已经不是 1978 年了 - 许多人的计算机内存超过 2 MB :-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM