简体   繁体   中英

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. So I decided to get my fingers wet and started out with "Hello, world.".

I did so on Windows 7 x64, btw.

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

Issuing cargo build and looking at the result in targets\debug I found the resulting .exe being 3MB. After some searching (documentation of cargo command line flags is hard to find...) I found the --release option and created the release build. To my surprise, the.exe size has only become smaller by an insignificant amount: 2.99MB instead of 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.

Can anyone elaborate on what Rust is compiling to, how it can be possible it produces such huge images from a 3-line program? Is it compiling to a virtual machine? Is there a strip command I missed (debug info inside the release build?)? 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! program will be compiled into your executable. This also includes the Rust runtime.

To force Rust to dynamically link programs, use the command-line arguments -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. 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.

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.

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.

I had two versions of the C hello world because I thought using puts() might link in fewer compilation units.

If you want to try reproducing it on Windows, here are the sources I used:

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!");
}

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.

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)
  2. Add the following to 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
  2. Run strip on the resulting binary.

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.

You can also use #![no_std] to remove Rust's libstd . See min-sized-rust for details.

When compiling with Cargo, you can use dynamic linking:

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:

strip target/release/<binary>

This will approximately halve the size of most binaries.

Install rust nightly - rustup toolchain install nightly , rustup default nightly

Now, make these changes in all the Cargo.toml files in your project.

Add cargo-features = ["strip"] before [package] at the top of the Cargo.toml

At the bottom, or between [dependencies] and [package] add,

[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

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

One of the reasons why the executables may be large is due to how the Rust compiler handles generics. Rust does NOT use polymorphism, but rather monomorphization . 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. One for Vec<i32> , one for Vec<bool> , etc...

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. So I decided to get my fingers wet and started out with Hello world...

I did so on Windows 7 x64, btw.

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

Issuing cargo build and looking at the result in targets\\debug I found the resulting .exe being 3MB. After some searching (documentation of cargo command line flags is hard to find...) I found --release option and created the release build. To my surprise, the .exe size has only become smaller by an insignificant amount: 2.99MB instead of 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.

Can anyone elaborate on what Rust is compiling to, how it can be possible it produces such huge images from a 3 liner program? Is it compiling to a virtual machine? Is there a strip command I missed (debug info inside the release build?)? 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. 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 :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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