Optimizing Rust Binary Size
tldr; Getting Zarf's
zarf-injector
binary down to < 512kb.
Rust Size Optimization
Stage 1's Existing Size + Behavior
Stage 1's Rust binary (zarf-injector
) src performs a few operations. It's main goal is to extract the seed Crane Docker Registry image tarball (seed-image.tar
) and our stage 2 binary (zarf-registry
) into a running Docker container.
- It merges all of the
zarf-payload-*
configmaps back into a single tarball, then extracts to the/zarf-stage2
directory. - It runs
chmod 755
on the/zarf-stage2
directory (due to windows compatibility issues).
This binary is compiled against the aarch64-linux-musl-ar
target for M1 Macs, and x86_64-unknown-linux-musl
for x86_64 Linux.
musl
is used instead ofgnu
because it's a smaller target.musl
is a libc implementation that's used in Alpine Linux, and is more lightweight thangnu
. It's also used in thescratch
image, which is the smallest image possible.
x86_64_size=$(du --si target/x86_64-unknown-linux-musl/release/zarf-injector | cut -f1)
echo "x86_64 binary size: $x86_64_size"
aarch_arm_64_size=$(du --si target/release/zarf-injector | cut -f1)
echo "aarch64 binary size: $aarch_arm_64_size"
---
x86_64 binary size: 528k
aarch64 binary size: 373k
Not bad! But we can do better.
For reference: running a standard
cargo build --release
on M1 with no optimizations is ~ 700kb.
Let's Get Small
Currently, this Rust binary has the following settings in the Cargo.toml
file:
[profile.release]
opt-level = "z" # Optimize for size.
lto = true
codegen-units = 1
panic = "abort"
strip = true
There is a great writeup here on optimizing Rust binary sizes.
From the preceding, we're already using strip
Symbols from Binary, Optimize For Size,
Enable Link Time Optimization, Reduce Parallel Code Generation Units, and
Abort on Panic.
Following some of the other suggestions, we can get the binary size down to 156kb.
In this implementation, we're re-compiling libstd
from nightly, instead of using the pre-compiled version. This is because the pre-compiled version isn't optimized for size, and isn't stripped.
"This is where
build-std
comes in. The build-std feature is able to compile libstd with your application from the source. It does this with the rust-src component that rustup conveniently provides." ~ min-sized-rust
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort \
--target aarch64-apple-darwin --release
aarch_arm_64_size=$(du --si target/aarch64-apple-darwin/release/zarf-injector | cut -f1)
echo "aarch64 binary size: $aarch_arm_64_size"
rustup +nightly target add x86_64-unknown-linux-musl
cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort \
--target x86_64-unknown-linux-musl --release
x86_64_size=$(du --si target/x86_64-unknown-linux-musl/release/zarf-injector | cut -f1)
echo "x86_64 binary size: $x86_64_size"
---
aarch64 binary size: 156k
x86_64 binary size: 176k
This accomplishes a 66% decrease in binary size!
There are further optimizations that can be made, but for now this is as unstable as we want to get.