Skip to content

build(profile): stop building vendored C/C++ and DWARF nobody reads - #512

Open
justin13888 wants to merge 1 commit into
masterfrom
chore/dev-profile-build-size
Open

build(profile): stop building vendored C/C++ and DWARF nobody reads#512
justin13888 wants to merge 1 commit into
masterfrom
chore/dev-profile-build-size

Conversation

@justin13888

Copy link
Copy Markdown
Collaborator

What

[profile.dev] was never set, so it ran at cargo's default opt-level = 0, debug = 2. Two costs follow from that, and between them they are most of what a worktree's target/ holds. This adds 31 lines to Cargo.toml and changes nothing else.

Why

1. Three packages were compiling vendored C/C++ at -O0 -g

cmake-rs and cc both derive their build type from the OPT_LEVEL/DEBUG env cargo hands the build script. Under an unset dev profile that is a Debug build of code that is not ours and is never stepped through from Rust:

dev profile release profile
libjxl.a 181.7 MiB 8.1 MiB
gamut-jxl-sys OUT_DIR 887 MiB 54 MiB

That cost is paid again in every binary that statically links the archive, and again in every superseded build-script output directory — which cargo never reclaims. A fresh tree that has run only build, test and clippy already holds three complete libjxl build trees (894 + 887 + 772 MiB) plus two Adobe DNG SDK trees. Running clippy after test added 1.61 GiB to build/ by itself. That is the mechanism by which a worktree drifts past 18 GiB.

The hand-written oracles under tooling/ already pass -DCMAKE_BUILD_TYPE=Release for exactly this reason. These three cannot: they delegate to jpegxl_src::build() and to cc::Build, neither of which accepts that define, so the cargo profile is the only lever they share.

This does not stop the accumulation — it makes each copy cost 54 MiB instead of 887 MiB, turning ~2.5 GiB of dead duplication into ~160 MiB.

2. Packed DWARF was copied into all 172 test binaries

An --all-features test build links 172 binaries and each got its own copy of its dependencies' debug info. A sampled 67 MiB test binary was 0.78 MiB of .text under 64.8 MiB of debug sections — 98%. Across a sample of 13, debug sections were 86.5% of bytes.

split-debuginfo = "unpacked" emits the DWARF once per codegen unit as .dwo files the binaries reference (verified: DW_AT_GNU_dwo_name resolves, and the 6138 .dwo files total 92 MiB). No debug information is lost. What changes is that a binary is no longer self-contained — one copied out of target/ loses its debug info — hence the note in the comment to debug and profile them where they were built.

Measurements

Cold cargo test --workspace --all-features --no-run, each from cargo clean. Both levers were also measured alone, and debug = "line-tables-only" was measured on top of the chosen pair:

config total 172 test bins largest bin
baseline 10.67 GiB 3.91 GiB 175 MiB
debug = "line-tables-only" alone 8.21 3.74 121 MiB
split-debuginfo alone 7.71 3.31 98 MiB
native at -O2 alone 5.39 2.13 66 MiB
this PR 4.85 1.70 35 MiB
this PR + line-tables-only 4.85 1.70 35 MiB

The last row is why the dev profile's debug level is left alone: lowering it buys nothing once the DWARF is split, so there is no reason to accept the debuggability tradeoff.

Full buildtestclippy → edit cycle, same seven stages before and after:

stage before after
cargo build 2.52 GiB / 57 s 1.09 GiB / 32 s
cargo test --no-run 10.67 / 296 s 5.87 / 100 s
cargo clippy 12.92 / 202 s 7.25 / 178 s
after edits (final) 14.15 GiB 8.48 GiB

−40% per worktree, and the cold test build is ~3× faster — the JXL differential tests had been running against an -O0 reference encoder, so this is a correctness-neutral speedup as well as a disk saving.

Validation

Run in a clean worktree at master with all submodules initialised:

  • mise run fmt-check — pass
  • mise run lint — pass
  • mise run test — pass, 3767 tests across 202 suites, 0 failures

Also verified: per-package overrides do reach the build-script env (OPT_LEVEL=2 DEBUG=false) and carry into the test profile; no profile package spec did not match any packages warning under plain cargo build, where the tooling/ oracles are absent from the compiled graph; and libjxl.a in this branch's own build directory is 8.1 MiB.

Notes for review

  • [profile.mutants] inherits test at debug = 0, so split debuginfo is a no-op there and the mutation gate is unaffected.
  • [profile.bench] and [profile.fast-debug] inherit release, so their native builds were already Release/RelWithDebInfo.
  • incremental/ is untouched by this change and is now the largest single component at 3.37 GiB. That is separate headroom, deliberately left out of scope.

An unset [profile.dev] means opt-level 0 with full debug info, and two
costs follow from it that together are most of a worktree's target/.

cmake-rs and cc both derive their build type from the OPT_LEVEL and DEBUG
that cargo hands the build script, so the three packages compiling vendored
C/C++ were building it at -O0 -g: libjxl.a at 181.7 MiB against 8.1 MiB for
the same archive at Release. That is paid again in every binary statically
linking it, and again in every superseded build-script output directory,
which cargo never reclaims — build, test and clippy leave three complete
libjxl trees behind (894 + 887 + 772 MiB). The oracles under tooling/
already pass -DCMAKE_BUILD_TYPE=Release; these three delegate to
jpegxl_src::build() and to cc::Build, neither of which accepts that define,
so the profile is the only lever they share.

Packed DWARF is then copied into each of the 172 test binaries an
--all-features build links. A sampled one was 0.78 MiB of .text under
64.8 MiB of debug sections. split-debuginfo = "unpacked" emits it once per
codegen unit as .dwo files the binaries reference, so no debug information
is lost; what changes is that a binary is no longer self-contained, which
is why the comment says to debug and profile them where they were built.

Measured over a full build + test + clippy + edit cycle from cargo clean:
14.15 GiB to 8.48 GiB, with the cold `cargo test --no-run` falling from
296s to 100s — the JXL differential tests had been running against an -O0
reference encoder. Lowering [profile.dev] debug to "line-tables-only" on
top of this measured byte-identical, so the debug level is left alone.
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