Skip to content

build: shrink the release binary with fat LTO and one codegen unit - #55

Merged
v1rtl merged 1 commit into
mainfrom
build/release-size
Nov 9, 2026
Merged

v1rtl merged 1 commit into
mainfrom
build/release-size

Conversation

@v1rtl

@v1rtl v1rtl commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

Cargo.toml had no [profile.release], so release builds used the defaults: 16 codegen units and no cross-crate LTO. The comment in release.yml saying LTO was on by default was wrong.

rustc gives each codegen unit its own copy of every closure and async body that unit uses. That left about a fifth of .text as duplicates:

  • the async body of main (about 138 KB) appeared three times;
  • bridge::run_leg, client::push_chunks_with_pool_inner and resolve_feed_root appeared 3–4 times each.

Fat LTO with a single codegen unit merges these copies.

Results

Measured on macOS arm64 with stripped binaries, the same form the release tarballs ship.

before after change
binary 13458360 9181064 -32%
.text 9353156 6844104 -27%
gzip -9 5913667 4413281 -25%
threaded wasm (before wasm-bindgen) 4911967 4153657 -15%

All figures are bytes.

  • The wasm build gets the single codegen unit but not LTO: cargo skips LTO when a lib target is both cdylib and rlib.
  • hoverfly bmt on a random 50 MB file gives the same roots with both binaries and takes the same time (about 0.27 s).
  • The release build and the threaded wasm release build both succeed with the new profile.

Why panics stay unwind

panic = "abort" would save another 2083856 bytes (7097208 total). But each peer session runs its libp2p swarm in its own tokio task (spawn_session_driver). With unwind, a panic in that task, for example a dependency choking on a peer's bytes, drops only that session. With abort it would end the whole upload, daemon or relay. AGENTS.md now records this so nobody switches it later.

Errors (Results, such as libp2p dial failures or resets) aren't affected either way. The setting only changes what happens on a panic.

Other changes

  • release.yml now builds only --bin hoverfly. sigcheck isn't shipped, so this avoids paying for a second fat-LTO link.

Notes

  • Release builds are slower. The single-threaded fat-LTO link affects release.yml, the bench workflows, CircleCI and the Dockerfile. Debug builds (cargo build) are unchanged.
  • The README's "7MB gzipped, 17MB unpacked x86 Linux binary" figures should be refreshed from the next release's real artifact. I measured on arm64 macOS only.

Cargo.toml had no [profile.release], so release builds used 16 codegen
units and no cross-crate LTO (release.yml's comment claiming LTO was on
by default was wrong). rustc gives each codegen unit its own copy of
every closure and async body it uses, which left about a fifth of .text
as duplicates: `main`'s async body alone appeared three times.

Stripped macOS arm64 binary: 13458360 -> 9181064 bytes (-32%), .text
9353156 -> 6844104. Threaded wasm: 4911967 -> 4153657 bytes (it gets the
single codegen unit but no LTO, since cargo can't LTO a cdylib+rlib lib).

Panics stay `unwind`. `abort` would save another ~2 MiB, but it would
turn a panic in one peer session's task into the end of the whole
process.

release.yml now builds only `--bin hoverfly`, so the unshipped sigcheck
doesn't cost a second fat-LTO link.
@v1rtl
v1rtl merged commit 199ac98 into main Nov 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant