Live data from Hacker News

Making Rust binaries smaller by default

kobzol.github.io

51–60 of 199 posts

Re: Making Rust binaries smaller by default

#51

Earlier quoted context omitted.

It's not a great sign that this bug existed for 7 years, people noticed, asked about it, wrote tickets, everybody agreed that it should be fixed but then nobody took the time to actually do it (and the eventual fix is a rather crude hack by just stripping the output binary instead of preventing that debug symbols slip into a release binary in the first place). Looks more like a systemic issue in the Rust development…

A vast majority of that 415 KB is due to the backtrace support, which is amazingly complicated. (See my other comment for specifics.) It will depend on some more machinaries from std, and a "simple" hello world can always panic if stdout is closed or so, therefore that part of std cannot be easily removed unless you are fine with useless backtraces. Also, Rust has no direct platform support unlike C. So everything ha…

For a statically linked libc I would expect that only the stdlib code that's actually used is included in the program (even without LTO).

For instance a statically linked C hello world for Linux via MUSL (cross-compiled with `zig cc -Os -target x86_64-linux-musl hello.c -o hello` because I'm currently on a Mac) is just 5 KBytes.

Re: Making Rust binaries smaller by default

#52

Earlier quoted context omitted.

A vast majority of that 415 KB is due to the backtrace support, which is amazingly complicated. (See my other comment for specifics.) It will depend on some more machinaries from std, and a "simple" hello world can always panic if stdout is closed or so, therefore that part of std cannot be easily removed unless you are fine with useless backtraces. Also, Rust has no direct platform support unlike C. So everything ha…

For a statically linked libc I would expect that only the stdlib code that's actually used is included in the program (even without LTO). For instance a statically linked C hello world for Linux via MUSL (cross-compiled with `zig cc -Os -target x86_64-linux-musl hello.c -o hello` because I'm currently on a Mac) is just 5 KBytes.

Musl doesn't have anything like Rust backtraces so it's not a fair comparison. By the way, glibc does have one [1], and I would be surprised if adding a backtrace printer doesn't significantly increase the binary size.

[1] https://www.gnu.org/software/libc/manual/html_node/Backtrace...

Re: Making Rust binaries smaller by default

#53
post #48

So what's in the remaining huge 415KB hello world program? Can we get rid of 90% of it a couple more times?

32Kb with the following configuration, on a linux target. It's not the default, you're using nightly, it's more complex to build and there are tradeoffs. You can even go less than that if that's your thing [profile.release] strip = true opt-level = "z" lto = true codegen-units = 1 panic = "abort" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target x86_64-unknown-linu…

These look like sensible defaults to me for release mode. What are the tradeoffs?

Re: Making Rust binaries smaller by default

#54
post #4

Initial binary size, like of a hello world, is a great indicator of how much abstraction the language has that youre also paying for. For example, java has to set up a constants pool, parse it from the classfile, run init and cinit, resolve references, allocatea a frame, and so on, just to get to the entrypoint. Compared to C without stdlib which needs to basically just run a few bytes to syscall write(). There are o…

Out of curiosity, I looked at a basic hello world in C, and compiled it naively using gcc on Ubuntu, by default the executable produced was 15960 bytes, but when adding the -Os option (from the man page: "-Os Optimize for size") to enable many optimisation flags including code size, I was able to reduce it to 15968.

  #include 
  int main() { printf("Hello, World!"); return 0;}
Of course, If I had read more than 3 words in the man page, the answer was easy to understand: "Os Optimize for size. -Os enables all -O2 optimizations except those that often increase code size", so you can't really get lower size than the default using only the optimization system, there's also "-finline-functions" included in -Os, but it won't help you in a Hello World.

Re: Making Rust binaries smaller by default

#55
post #2

> I can imagine a situation where a seasoned C or C++ programmer wants to try Rust, compiles a small program in release mode, notices the resulting binary size, and then immediately gives up on the language and goes to make fun of it on the forums. While I'm not a seasoned C or C++ programmer I have definitely done this to a few new languages when playing around with them. My thought was "If helloworld.rust is this l…

That’s almost never the case, though. Imagine if a C binary statically linked in libc. Adding a page full of more C to its code would only grow it a tiny amount.

A statically linked C hello world is just 5 KBytes though (with MUSL on Linux). Static linking doesn't mean that the entire library is included in the binary, only code that's actually used (details vary depending on how the library was created, and how the program was linked, but those 5 KB are with defaults).

Re: Making Rust binaries smaller by default

#56

Earlier quoted context omitted.

The Rust compiler statically links everything into a single binary. This can be changed, but is generally discouraged as there are no ABI stability guarantees at this point.

You can use the C ABI if you want something stable, and there are custom crates that will help with translating from the Rust to the C ABI. (This also helps because the resulting shared objects can potentially be FFI'd with from any language, not just Rust. They might as well be plain C libraries as far as anyone is concerned, only with the usual Rust safety requirements.)

Also, even in "the" C ABI provided by Rust out of the box there's a certain amount of "Well, this is probably what your C compiler does here, but there's no requirement" rather than an actual hard ABI document. A lot of the "We're ABI stable" claims in C and C++ are "We daren't change anything or stuff breaks" which isn't so much an ABI as it is paralysis and Rust is confident it doesn't want to do that.

Re: Making Rust binaries smaller by default

#57
post #54
post #4

Initial binary size, like of a hello world, is a great indicator of how much abstraction the language has that youre also paying for. For example, java has to set up a constants pool, parse it from the classfile, run init and cinit, resolve references, allocatea a frame, and so on, just to get to the entrypoint. Compared to C without stdlib which needs to basically just run a few bytes to syscall write(). There are o…

Out of curiosity, I looked at a basic hello world in C, and compiled it naively using gcc on Ubuntu, by default the executable produced was 15960 bytes, but when adding the -Os option (from the man page: "-Os Optimize for size") to enable many optimisation flags including code size, I was able to reduce it to 15968. #include int main() { printf("Hello, World!"); return 0;} Of course, If I had read more than 3 words i…

    zig cc -Os -target x86_64-linux-musl hello.c -o hello
...which basically calls Clang under the hood, but comes with out-of-the-box cross-compilation support for Linux and MUSL creates a 5136 bytes executable.

Re: Making Rust binaries smaller by default

#58
post #48

Earlier quoted context omitted.

32Kb with the following configuration, on a linux target. It's not the default, you're using nightly, it's more complex to build and there are tradeoffs. You can even go less than that if that's your thing [profile.release] strip = true opt-level = "z" lto = true codegen-units = 1 panic = "abort" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target x86_64-unknown-linu…

These look like sensible defaults to me for release mode. What are the tradeoffs?

opt-level = "z" may be slightly slower than O2/O3. lto = true and lto-units = 1 makes the final linking unbearably slow for large programs.

These are still okay, but then `panic = "abort"` shakes a lot of code off by making the final executable unable to print a nice stack trace (even without symbols) and instead immediately traps/abort()-s.

Edit: and GP also used "panic-immediate-abort", which also removes the dependency to std::fmt::format!() because it now silently abort()s without even printing an error string.

Re: Making Rust binaries smaller by default

#59
post #29

"Rust tries to appeal to programmers coming from many different backgrounds, and not everyone knows that something like stripping binaries even exists."

It's not ideally written but it's safe to assume if someone compiles a quick test program for that purpose and it ends up being 4MB, they might just move on to other options instead of starting to think about how they can manually reduce the binary size. When evaluating languages, you rarely look foor good enough results to work around.

My machine has 32GB of RAM, why should I care about binary size? For embedded, sure. But most people don't write for embedded.

If it was like 1GB, sure. But it aint.

Re: Making Rust binaries smaller by default

#60

Earlier quoted context omitted.

It's not a great sign that this bug existed for 7 years, people noticed, asked about it, wrote tickets, everybody agreed that it should be fixed but then nobody took the time to actually do it (and the eventual fix is a rather crude hack by just stripping the output binary instead of preventing that debug symbols slip into a release binary in the first place). Looks more like a systemic issue in the Rust development…

> nobody took the time to actually do it Because in the end almost nobody actually cares about it enough to create a fix. Small binaries are great, but people care mainly about how fast it compiles and how fast it runs, and in the few cases where the binary size is important it was already possible to shrink it significantly (more than with this new change). In my entire life I have never heard a user complain about…

What bothers me more than the actual binary size (yes ok, the remaining 400 KBytes may actually be justified as pointed out by @lifthrasiir) is that the additional 3.5 MBytes baggage of stdlib debug info was just dead weight. They could just as well have included 3.5 MBytes of noise in each executable.
Post reply on HN