Shrinking a Rust binary output from 18mb to 7mb
Simple tweaks to the options in the release profile can dramatically reduce the footprint of the resulting binary
After spotting an 18mb binary lurking in the node_modules
folder of a project I'd released with napi-rs
, I was
curious about how much I could reduce it.
I was convinced of two things:
- First, that I'd only be able to trim a megabyte here or there with a combo of various release options.
- Second, that any larger wins would come from considering which 3rd-party crates I'm using and swapping them out for lighter weight ones.
In the end, I was so wrong on the first part, that I thought it was worth sharing in this post. If I end up tweaking 3rd party dependencies to further reduce binary size, I'll be sure to document that process too.
Journey
I was reading this post Trimming down a rust binary in half , but it seemed like swapping out dependencies was a much bigger task than I wanted to take on right now. The post eventually links out to min-sized-rust though—so I started copy/pasting options from there seeing where it led me 🤣.
Impact
Well, with zero changes to dependencies, I cut 11mb from the binary size! I've documented each option I added and what difference it made to the output size 🤌
In each case, I'm just adding another line within the [profile.release]
section of Cargo.toml, and re-running this:
cargo build --release
Note: If you're using
napi-rs
, these same optimizations will apply when you run the relevant build command ✅
Cargo Options Applied | Binary Size | Difference | |
---|---|---|---|
1 | None | 18mb | - |
2 | [profile.release] strip = true | 14mb | -4mb |
3 | [profile.release] strip = true opt-level = "z" | 12mb | -2mb |
4 | [profile.release] strip = true opt-level = "z" lto = true | 8.1mb | -3.9mb |
5 | [profile.release] strip = true opt-level = "z" lto = true codegen-units = 1 | 7.9mb | -0.2mb |
6 | [profile.release] strip = true opt-level = "z" lto = true codegen-units = 1 panic = "abort" | ✅ 7.0mb | -0.9mb |
Result - 11mb smaller overall!
Now, when I publish my program to npm
, users are only downloading a 7mb binary, compared to an 18mb one 🎉. Of course, I can further
shrink this—I'm not exactly happy with it being 7mb, but I did find it interesting that with zero code/dependency changes I was still
able to shrink the size so much!
Note: In my use-case, I'm optimizing for size over performance (with opt-level = "z"
) since I'm building a development server where
raw-performance is not the primary concern
Copy/Paste
[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
Legend: Explanation of Cargo Options (this part is from ChatGPT)
Option | Explanation |
---|---|
strip = true | Removes all debug symbols from the binary, which reduces the size by eliminating unused metadata. |
opt-level = "z" | Optimizes for binary size instead of performance, producing a smaller binary at the cost of runtime speed. |
lto = true | Enables Link Time Optimization, which performs cross-module optimization at the linking stage, reducing size and improving performance. |
codegen-units = 1 | Limits the compiler to use a single code generation unit, allowing for better optimization across the entire crate at the expense of compilation time. |
panic = "abort" | Configures the binary to abort on panic instead of unwinding, saving space by not including unwinding code. |