Optimizing Rust Binary Size
tldr; Getting Zarf's
zarf-injectorbinary 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-stage2directory. - It runs
chmod 755on the/zarf-stage2directory (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.
muslis used instead ofgnubecause it's a smaller target.muslis a libc implementation that's used in Alpine Linux, and is more lightweight thangnu. It's also used in thescratchimage, 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 --releaseon 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-stdcomes 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.