I finished a small Rust project (about 300 lines of code) with the following dependencies:
When using cargo build --release
without further configuration, a 2.942.744 bytes (= 2,8 MiB) binary is generated. I tried to optimize this by enabling Link Time Optimization (LTO) in my Cargo.toml
:
[profile.release]
lto = true
To my surprise, the binary grows, with a new size of 3.848.288 bytes (= 3,7 MiB).
How can this be explained? Is there any mistake I made configuring Cargo?
Rust uses monomorphization and static linking, which does tend to cause the size of binaries to be larger than languages which do not do those things, however the benefits of these techniques are generally seen as outweighing the file size impact.
LTO provides a performance boost for all the compilers. With small projects, this boost probably wouldn't be noticeable, but for big ones this option definitely makes a difference.
Enable Link Time Optimization (LTO) By default, Cargo instructs compilation units to be compiled and optimized in isolation. LTO instructs the linker to optimize at the link stage. This can, for example, remove dead code and often times reduces binary size.
By default, Rust produces fairly large binaries, which may be annoying when building a RAT. A larger executable means more resources used on the system, longer and less reliable downloads, and easier to be detected. We will see a few tips to reduce the size of a Rust executable.
LTO means Link-Time Optimization. It is generally set up to use the regular optimization passes used to produce object files... at link time instead, or in addition.
A compiler does not inherently optimize for speed over size or size over speed; and therefore neither does LTO.
Instead, when invoking the compiler, the user selects a profile. For rustc
:
O0
, O1
, O2
and O3
are optimizing for speed.Os
and Oz
are optimizing for size.LTO can be combined on top of any optimization level, and will follow the selected profile.
By default, the [release]
profile instructs cargo
to invoke rustc
with O2
or O3
, which attempts to optimize for speed over size.
In particular, O3
can rely quite heavily on inlining. Inlining is all about giving more context to the optimizer, and therefore more optimization opportunities... LTO offers more chances to apply inlining (more known functions), and here it appears that more inlining happened.
It also reduces size. Possibly.
By giving more context, the optimizer/linker can realize that some portion of the code or dependencies are not used at all, and therefore can be elided.
If using Os
or Oz
, the size is near certain to go down.
If using O2
or O3
, unused code is removed while inlining adds more code, so it's quite unpredictable whether the end result is bigger or smaller.
LTO gives the optimizer a better opportunity at optimizing, so it's a good default for Releases.
Just remember that cargo
leans toward speed over size by default, and if this does not suit you, you may want to select another optimization direction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With