Live data from Hacker News

Making Rust binaries smaller by default

kobzol.github.io

71–80 of 199 posts

Re: Making Rust binaries smaller by default

#71

It's great to see rust maturing. That people care about binary size and work to improve it is a good sign.

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…

If you read the article you would know that this is just about changing defaults. You already could achieve this by editing your project's Cargo.toml. As is documented and discussed in several places over the years (google "shrink rust binary size").

The `strip` was added to rust nightly in 2020.

1: https://github.com/johnthagen/min-sized-rust

2: https://kerkour.com/optimize-rust-binary-size

3: https://rustrepo.com/repo/johnthagen-min-sized-rust

4: https://sing.stanford.edu/site/publications/rust-lctes22.pdf

5: https://arusahni.net/blog/2020/03/optimizing-rust-binary-siz...

Re: Making Rust binaries smaller by default

#72

Earlier quoted context omitted.

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

Comparing with the current Cargo default [1]: • `panic = "abort"` means that any panic terminates the program. This is not always desirable because you may want to catch and recover from panics, particularly in long-running servers. • `strip = true` means that anything depending on DWARF would no longer work. Backtraces won't work, but also unwinding will no longer work (so this is disastrous if you haven't set `pani…

All makes sense, but I don't agree with:

> This is not always desirable because you may want to catch and recover from panics, particularly in long-running servers.

IMHO a panic implies that execution cannot continue under any circumstances, and even any attempts for a graceful shutdown might be futile (if recovery is possible it shouldn't be a panic but done through regular error handling).

For a server process the best reaction to a panic would mean abort and clean restart.

> A single cgu will significantly increase the compilation time, while allowing a bit more optimization.

Increased build time is acceptable for release mode IMHO.

Re: Making Rust binaries smaller by default

#73
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…

This program is not same to the Rust version. A more faithful version, assuming glibc, would look like this:

    #include 
    #include 
    #include 
    #include 
    
    void print_backtrace(void) {
        void *traces[50];
        char **symbols;
        int num_traces, i;
    
        num_traces = backtrace(traces, sizeof(traces) / sizeof(*traces));
        strings = backtrace_symbols(traces, num_traces);
        if (!strings) return;
        for (i = 0; i 
While this is still substantially different (for example, Rust's I/O buffering is different from C), this should be enough to demonstrate that this comparison is very unfair.

Re: Making Rust binaries smaller by default

#74
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…

    $ gcc -Os a.c && ls -al a.out && size
    -rwxr-xr-x 1 user user 15952 Jan 24 17:49 a.out
       text    data     bss     dec     hex filename
       1316     584       8    1908     774 a.out
    $ gcc -s -Os a.c && ls -al a.out && size
    -rwxr-xr-x 1 user user 14472 Jan 24 17:50 a.out
       text    data     bss     dec     hex filename
       1316     584       8    1908     774 a.out
    $ gcc -s -Os -fuse-ld=lld a.c && ls -al a.out && size
    -rwxr-xr-x 1 user user 4552 Jan 24 17:50 a.out
       text    data     bss     dec     hex filename
       1199     528       1    1728     6c0 a.out

Re: Making Rust binaries smaller by default

#75

Earlier quoted context omitted.

Rust can't do this very well because it doesn't build std as part of the project, that's still an unstable option. It's linking object code.

C doesn't either, but statically linking still pulls only the object files which are actually needed into the executable.

I think that's the main reason why every stdlib function lives in its own source file in MUSL:

https://git.musl-libc.org/cgit/musl/tree/src/stdio

Not sure if this can easily be replicated in Rust though and LTO must be used instead for dead code removal.

Re: Making Rust binaries smaller by default

#76
post #54

Earlier quoted context omitted.

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…

This program is not same to the Rust version. A more faithful version, assuming glibc, would look like this: #include #include #include #include void print_backtrace(void) { void *traces[50]; char **symbols; int num_traces, i; num_traces = backtrace(traces, sizeof(traces) / sizeof(*traces)); strings = backtrace_symbols(traces, num_traces); if (!strings) return; for (i = 0; i While this is still substantially differen…

Building your code (I fixed a few typos, strings -> symbols, fprintf(" -> fprintf(stderr, ") with

    gcc -s -Os -fuse-ld=lld a.c && ls -al a.out
leads to a 5496 bytes ELF though. Which is not much larger than just printf("Hello World!\n"), see sibling comment.

I think the point is C "cheated" by including a lot of goodness (format, backtrace etc) in the shared library so they does not have to be copied to each binary.

Re: Making Rust binaries smaller by default

#77
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…

They're really gonna panic when they deploy their first qt app then.

Statically linked Qt 4.x wasn't actually so bad. But that's not compatible with the LGPL license for closed-source apps, you had to get a commercial license. The problem with DLL based Qt apps is that you ship a ton of code that's never called.

Re: Making Rust binaries smaller by default

#78

Earlier quoted context omitted.

Comparing with the current Cargo default [1]: • `panic = "abort"` means that any panic terminates the program. This is not always desirable because you may want to catch and recover from panics, particularly in long-running servers. • `strip = true` means that anything depending on DWARF would no longer work. Backtraces won't work, but also unwinding will no longer work (so this is disastrous if you haven't set `pani…

All makes sense, but I don't agree with: > This is not always desirable because you may want to catch and recover from panics, particularly in long-running servers. IMHO a panic implies that execution cannot continue under any circumstances, and even any attempts for a graceful shutdown might be futile (if recovery is possible it shouldn't be a panic but done through regular error handling). For a server process the…

I remember my disappointment when I found out panics could be “caught” and that I was still living in the world of exceptions

Re: Making Rust binaries smaller by default

#79

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

At this moment, I am compiling GCC 10.5 on a RPi Model B with 256M of RAM. The root filesystem is on a ramdisk embedded in the kernel. The SD card from which I booted is read-only.

The compilation runs not from the SD card but from a chroot to a large external drive plugged into the RPi via USB.

Once I have GCC and rest of the toolchain built, I then use it to compile custom kernels with embedded ramdisks.

https://en.wikipedia.org/wiki/Self-hosting_(compilers)

Wish I had a computer with 32GB RAM but I don't.

I use strip -s every day.

Re: Making Rust binaries smaller by default

#80

Earlier quoted context omitted.

Comparing with the current Cargo default [1]: • `panic = "abort"` means that any panic terminates the program. This is not always desirable because you may want to catch and recover from panics, particularly in long-running servers. • `strip = true` means that anything depending on DWARF would no longer work. Backtraces won't work, but also unwinding will no longer work (so this is disastrous if you haven't set `pani…

All makes sense, but I don't agree with: > This is not always desirable because you may want to catch and recover from panics, particularly in long-running servers. IMHO a panic implies that execution cannot continue under any circumstances, and even any attempts for a graceful shutdown might be futile (if recovery is possible it shouldn't be a panic but done through regular error handling). For a server process the…

>For a server process the best reaction to a panic would mean abort and clean restart.

I suppose you could have a panic in one thread whilst others are still running fine. You may just want to shut those other threads down gracefully.

Post reply on HN