News for hackers. Stuff that matters.
Re:Making Rust binaries smaller by default by anonymoushn on Wednesday January 24, 2024 @02:30AM (#39114602)
So what's in the remaining huge 415KB hello world program? Can we get rid of 90% of it a couple more times?
Re:Making Rust binaries smaller by default by lifthrasiir on Wednesday January 24, 2024 @03:08AM (#39114855)
Buffered I/O and synchronization primitives to avoid corrupting the buffer. The underlying vector implementation and memory allocator. Panic support and backtrace printer, which includes a Rust-specific name demangler and a not-so-small DWARF parser in Linux. Path support because backtraces would include source file paths. Zlib-compatible decompressor because ELF allows compressed sections (!). Then you have several formatters that are often pretty large (e.g. f32 and f64 would add at least 30 KB of binary, and some depend on Unicode grapheme clusters). They are all essential for edge cases that can happen even for such a simple program.
Re:Making Rust binaries smaller by default by temac on Wednesday January 24, 2024 @03:15AM (#39114901)
Why does it need to parse elf ?
Re:Making Rust binaries smaller by default by lifthrasiir on Wednesday January 24, 2024 @03:23AM (#39114956)
The stack frame is a list of return addresses which have to be translated to a file name and line number. Such debug information is within a specific ELF section in the predefined format. In this simple case you may be able to hard-code the offset to the section (and guarantee that it was never compressed), but any additional C or Rust library will break this assumption, so a general parser has to be included.
Re:Making Rust binaries smaller by default by nsteel on Wednesday January 24, 2024 @07:41AM (#39116565)
Sorry if this is dumb but if the idea is stripping debug stuff, why would a parser for translating return addresses into file names and line numbers be useful?

EDIT: oh, ok, so I guess it's because strip is "debuginfo" here, rather than "true".

Re:Making Rust binaries smaller by default by kobzol on Wednesday January 24, 2024 @08:41AM (#39117208)
There is some work underway to enable removing the backtrace generation/parsing from Rust binaries. It's hardcoded for now though.
Re:Making Rust binaries smaller by default by owl57 on Wednesday January 24, 2024 @05:21AM (#39115715)
> several formatters that are often pretty large (e.g. f32 and f64 would add at least 30 KB of binary …)

I know floats are full of scary corner cases, but… Assuming tens of bytes per an if statement, hundreds of corner cases just in formatting? Is it really that bad?

Re:Making Rust binaries smaller by default by lifthrasiir on Wednesday January 24, 2024 @05:30AM (#39115761)
A naive implementation would be small, but a correct implementation (with some concrete definition of "correct") will need a sizable data table. I know this because I wrote that part of code [1] and somehow it is still in the std even though other algorithms now exist [2].

[1] https://github.com/rust-lang/rust/pull/24612

[2] https://github.com/rust-lang/rust/issues/52811

Re:Making Rust binaries smaller by default by riquito on Wednesday January 24, 2024 @03:53AM (#39115157)
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-linux-gnu --release

As mentioned in another thread, I've simply followed https://github.com/johnthagen/min-sized-rust

Re:Making Rust binaries smaller by default by flohofwoe on Wednesday January 24, 2024 @04:16AM (#39115308)
These look like sensible defaults to me for release mode. What are the tradeoffs?
Re:Making Rust binaries smaller by default by rfoo on Wednesday January 24, 2024 @04:27AM (#39115369)
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 by josephg on Wednesday January 24, 2024 @06:20AM (#39116012)
> 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.

I just tried this. The stack traces on panic seem more or less the same with or without panic = "abort" in Cargo.toml.

For example, this program:

    fn main() {
        let v = vec![1, 2, 3];
        v[99];
    }
Compiled with panic="abort" outputs this stack trace:

    $ RUST_BACKTRACE=1 cargo run --release
      Compiling rust-panic v0.1.0 (/Users/seph/temp/rust-panic)
        Finished release [optimized] target(s) in 1.82s
        Running `target/release/rust-panic`
    thread 'main' panicked at src/main.rs:4:6:
    index out of bounds: the len is 3 but the index is 99
    stack backtrace:
      0: _rust_begin_unwind
      1: core::panicking::panic_fmt
      2: core::panicking::panic_bounds_check
      3: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
      4: rust_panic::main
    note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
    [1]    95405 abort      RUST_BACKTRACE=1 cargo run --release
Weirdly, in this test if I don't strip the binary, I get a larger binary size with panic="abort" than when I leave that out. That is surely due to a bug somewhere.
Re:Making Rust binaries smaller by default by ponkpanda on Wednesday January 24, 2024 @08:19AM (#39116939)
You're not adding the +nightly switch and the required flags to compiled std. Just setting panic=abort in cargo.toml isn't enough.

One can get binaries pretty damn small (low-mid tens of kilobytes for a basic cli program doing something like hashing of a file).

Problem I've found with manually compiling std (which has ancillary benefits of being able to compile to a specific uarch) is it can break the compilation process when bringing in third-party deps. The config.toml (stored in $PROJECT_ROOT/.cargo) overrides cargo's behaviour for all dependencies as well - which may break those compilations.

Tbh, it's one reason I don't particularly rate the rustc+cargo toolchain - but for most people writing regular applications: just being able to do ```cargo build -r``` and not care about binary size, uarch optimization or custom llvm/rustc optimizations (PGO etc), most won't care.

Re:Making Rust binaries smaller by default by lifthrasiir on Wednesday January 24, 2024 @06:23AM (#39116038)
> `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.

I believe it does print backtraces then terminate, since the backtrace is printed via panic hooks, which happen before the actual unwinding.

Re:Making Rust binaries smaller by default by vacuity on Wednesday January 24, 2024 @01:20PM (#39120746)
Is the backtrace performed by the OS and that's why it still works? And the debug info needs to still be provided, just not an unwinding mechanism?
Re:Making Rust binaries smaller by default by exDM69 on Wednesday January 24, 2024 @04:36AM (#39115427)
Additionally the panic = "abort" disables stack unwinding, and running destructors when panic occurs, so it's a big change to the semantics of the program.

It's comparable to C++ land -fno-exceptions (not exactly, but similar).

Re:Making Rust binaries smaller by default by flohofwoe on Wednesday January 24, 2024 @04:42AM (#39115470)
IMHO when a piece of code decides to panic it should only happen when execution cannot continue under any circumstances, and in such cases all bets are off whether any cleanup code would actually still work. A hard abort might indeed be the best option.
Re:Making Rust binaries smaller by default by edflsafoiewq on Wednesday January 24, 2024 @05:22AM (#39115721)
That seems overly pessimistic (or perhaps overly optimistic about code that does not panic). Panics mostly exist to guard code from entering into such unrecoverable states, eg writing past the end of an array.
Re:Making Rust binaries smaller by default by flohofwoe on Wednesday January 24, 2024 @06:41AM (#39116150)
For me the difference would be: if the write past the array has been detected before it happens (via a range check) it would be a regular error which which can be handled, while a panic would be "oops somebody else has written past the end of the array" (e.g. a hitting a canary check), which should abort immediately because at that point it's no longer guaranteed that any recovery code would even work or just make things worse (e.g. trying to flush data from memory back to disk, but that data might have been corrupted too).
Re:Making Rust binaries smaller by default by lifthrasiir on Wednesday January 24, 2024 @07:24AM (#39116447)
You have a good point and lock poisoning had the same reasoning, but wasn't really popular [1]. (In hindsight the poisoning itself was a great idea but should have been decoupled from locks.) Compared to other languages with similar constructs, Rust panic is probably regarded as less fatal because an incorrect but safe Rust code tends to have a limited reach.

Ah, and it should be also noted that some non-fatal signals were also delivered only via panic. The best-known example is a memory allocation failure, which is recoverable in Rust but needed unwinding for a long time. Nowadays you have an unsafe but non-unwinding alternative.

[1] https://blog.rust-lang.org/2020/12/11/lock-poisoning-survey....

Re:Making Rust binaries smaller by default by edflsafoiewq on Wednesday January 24, 2024 @01:28PM (#39120858)
In Rust, out of bound array accesses are detected before they happen and handled by panicking.
Re:Making Rust binaries smaller by default by junon on Wednesday January 24, 2024 @04:38AM (#39115442)
Abort panic strategy is common in firmware and osdev, for anyone wondering if there are other use cases.
Re:Making Rust binaries smaller by default by chrismorgan on Wednesday January 24, 2024 @04:35AM (#39115419)
opt-level z would not be a reasonable default: you should normally optimise for speed, not binary size.

lto and codegen-units=1 have a huge compile time cost. For release for general distribution you should tend to favour them, but the release profile isn’t just about that activity (especially because debug/opt-level<2 is often just too slow to use while developing). You commonly want to create another profile for production distribution.

Abort on panic changes runtime behaviour by stopping you from catching panics, which will completely break some programs, and harm the failure mode of others, so that e.g. one defective route on a web server will suddenly take the entire website down for everyone (or, if you have a supervisor that can restart the server, at least disrupt it for everyone).

Re:Making Rust binaries smaller by default by flohofwoe on Wednesday January 24, 2024 @04:39AM (#39115443)
> you should normally optimise for speed, not binary size

IME optimize for speed vs size usually isn't as clear cut as the name says though, sometimes smaller code does indeed run faster, but in most cases I've seen there's not much of a difference between -O3, -O2, -Os and -Oz.

Re:Making Rust binaries smaller by default by kibwen on Wednesday January 24, 2024 @07:16AM (#39116391)
Weirdly, most of the time I find that -O3 produces smaller binaries than -Oz.
Re:Making Rust binaries smaller by default by lifthrasiir on Wednesday January 24, 2024 @04:40AM (#39115453)
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 `panic` above). The actual proposal has `strip = "debuginfo"` instead, so unwinding will work while backtraces won't.

• `codegen-units = 1` is the number of concurrent compilation jobs (cgu) in the LLVM codegen phase. A single cgu will significantly increase the compilation time, while allowing a bit more optimization. Otherwise this is okay.

• `lto = true` enables Rust-specific link-time optimizations across crates. The actual benefit depends on the set of crates linked, but it is significantly slower that many large enough projects wouldn't want it. It does benefit small programs like the "Hello, world" program the most though.

• `opt-level = "z"` is same to C/C++ `-Oz` and the same pros and cons apply.

[1] https://doc.rust-lang.org/cargo/reference/profiles.html#rele...

Re:Making Rust binaries smaller by default by flohofwoe on Wednesday January 24, 2024 @04:45AM (#39115485)
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 by lifthrasiir on Wednesday January 24, 2024 @05:35AM (#39115784)
> 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).

Rust panic is just a C++ exception in its implementation, and not every C++ programmer would terminate a process when an exception is thrown. Of course Rust panic is more resillient because Rust provides a memory safety and the logic error can be reasonably bounded as a result.

> Increased build time is acceptable for release mode IMHO.

I think the slowest possible configuration is at least 3x slower than the default, and that's too slow to be acceptable for most people. But you can always tune them up if you want---please note that this issue is all about defaults.

Re:Making Rust binaries smaller by default by robinsonrc on Wednesday January 24, 2024 @05:01AM (#39115587)
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 by kibwen on Wednesday January 24, 2024 @07:13AM (#39116364)
Panics have to be catchable because it's undefined behavior for Rust to unwind through C code, so a Rust library that exposes a C API has to stop panics at the FFI boundary and propagate errors in a C-compatible way. It's not "catching" in the Java sense; nobody is using catch_panic to implement resumable error-handling.
Re:Making Rust binaries smaller by default by josephg on Wednesday January 24, 2024 @06:10AM (#39115977)
Oh no, I didn't know this. I'm adding panic = "abort" to all my projects. If I wanted exceptions I wouldn't be using rust.
Re:Making Rust binaries smaller by default by lifthrasiir on Wednesday January 24, 2024 @06:21AM (#39116024)
That's actually not too bad if you know you don't need to recover from panic. I kinda hope that `panic = "abort"` is indeed default unless overridden by dependencies, say, tokio...
Re:Making Rust binaries smaller by default by formerly_proven on Wednesday January 24, 2024 @06:33AM (#39116108)
Re:Making Rust binaries smaller by default by IshKebab on Wednesday January 24, 2024 @08:55AM (#39117373)
You aren't. They're only caught in special cases, e.g. if a thread panics and you want to propagate that to other threads, to catch panics in unit tests, to avoid unwinding across FFI, etc.

You shouldn't use them as a general exception mechanism. They aren't the same, even if under the hood they both use stack unwinding.

Re:Making Rust binaries smaller by default by vacuity on Wednesday January 24, 2024 @01:08PM (#39120606)
Naively, what is the difference between panics and exceptions? Is it the philosophy on when/how to use them?
Re:Making Rust binaries smaller by default by IshKebab on Thursday January 25, 2024 @11:57AM (#39131719)
Yes, and also exceptions can have arbitrary value associated with them. You aren't going to get far using panics as a general error handling mechanism because there's only one type of panic.
Re:Making Rust binaries smaller by default by tonyedgecombe on Wednesday January 24, 2024 @05:19AM (#39115707)
>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.

Re:Making Rust binaries smaller by default by pornel on Wednesday January 24, 2024 @10:49AM (#39118736)
> For a server process the best reaction to a panic would mean abort and clean restart.

That's often infeasible, and creates a major DoS/reliability problem.

The server may be processing hundreds of different requests at the same time, and panic=abort will kill all of them. That creates a visible failure to other unrelated clients of the server, not just the offending request.

If you try to fix that and retry aborted requests, you'll retry the panic-inducing request and cause another failure (and it'll take several restarts to bisect out the offending request).

Plus a restart may be costly, require loading data, warming up caches, etc.

It's just way cheaper to catch a panic and return 500 to the offending request. Rust guarantees to panic before anything terrible memory-corrupting happens. Even if you don't trust it and would prefer to restart anyway, you have an option to gracefully hand over the traffic before the restart.

Re:Making Rust binaries smaller by default by kobzol on Wednesday January 24, 2024 @02:43AM (#39114690)
Mostly the formatting machinery from stdlib. You can go sub 100 KiB or even less without it, but unless you target embedded, there's really a reason to do that, IMO.