tinymold exists to make Rust builds faster and smaller, so every claim it makes has to come with a number anyone can reproduce. This directory holds the harness, the corpus it builds, and the results it has produced.
Each benchmark measures tinymold against plain cargo, sccache, or both, so together they are the baseline every later phase has to beat:
-
commands:cargo build,clippy,testandllvm-cov, per mode, cold (empty target and cache), then warm (target deleted, cache kept). Results inCOMMANDS.md; seecommands.py. -
scenarios: the full edit-loop matrix, and the main scoreboard. For every corpus project and every mode (baseline,sccache, andtinymoldonce its rustc wrapper exists), it runs eight cases:- a clean build;
- a no-op rebuild;
- a comment edit;
- a private function body edit;
- a public signature edit;
- a second worktree of the same commit;
- a branch switch A→B→A;
cargo test --no-run.
For each case it records wall time (median, min, max), link time from a tiny timing linker wrapper the harness installs, units compiled versus fresh (from
cargo build -v),target/bytes split by kind, and cache hits. -
dedup: builds two worktrees of each project and reports how much of the second'starget/is byte-identical to the first's at three levels: whole files, rlib members, and DWARF sections split out of object files. The gap between levels is what chunk-level storage could save beyond whole-file dedup. -
baseline: plain cargo, default linker, no cache. Per corpus project: a clean build, a no-op rebuild, a one-line edit, an mtime-only change to every source file (what a branch switch or a cache restore does to cargo), building the tests, and a clean build in a second worktree at another path. It also measurestarget/size and how many of the second worktree's artifact bytes are identical to the first's, which is what a shared store would dedupe. -
cache: clean builds with a compile cache. No cache, then an empty cache (the dry run that fills it), then the warm cache (the cached run), then the warm cache from a second worktree at another path. The cache is sccache, today's reference, which tinymold's cache has to beat on hit rate and wall time. -
link: the edit loop per linker. Append a comment to one source file and rebuild, once per linker available on the machine. That edit recompiles the leaf crate and relinks the whole binary, and on ripgrep about half of that rebuild is the linker (research). -
cas: the cost of a cache hit. Restore one large artifact into a fresh path: a plain byte copy againsttinymold cas getby reflink, hardlink, and copy. A hit only beats a rebuild if restoring doesn't copy bytes. -
hit: the cost of a cache hit on units with many outputs. Every source file is touched and the project rebuilt through the wrapper, so each workspace unit is a hit. Incremental units with unpacked split debuginfo keep one object file per codegen unit, so restoring one places hundreds of files.--against <rev>builds another revision's tinymold and alternates the two on the same store;TINYMOLD_TRACEtimes each restore apart from cargo. -
ci: a fresh CI runner at the same path, runningcargo test --no-run. It compares four setups:- a cold build;
target/restored from an archive with every source touched, as atarget/cache such as Swatinem/rust-cache does;- tinymold's store restored from an archive (the GitHub Action's default backend);
- tinymold fed only by a local bazel-remote container (the remote backend).
Downloading an archive from GitHub is not timed, so its size is listed instead. On bat the tinymold modes trail the
target/cache. Cargo re-runs every build script in an emptytarget/, and bat's compile C code, whichRUSTC_WRAPPERnever sees.
Generated by bench/report.py from the JSON files in results/.
Each heading lists the machine and toolchain the numbers came from. A number
without its machine is not a result.
Build, clippy, tests and coverage, cold and then warm from the cache with the target directory deleted: see COMMANDS.md.
Edit-loop scenarios (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 3 runs, 2026-10-14)
Pinned: cargo build jobs: 18; target filesystem: apfs; linker: cc, rustc's default, behind a timing wrapper; linker threads: linker default; the platform cc linker takes no thread flag; output reuse: rustc default: each link writes a fresh output; incremental: on for baseline; off under sccache, which refuses it.
Each cell: median wall time, then median link time, then units compiled (cargo's count: a unit served from a cache still shows as compiled). Under sccache and tinymold, hits counts rustc invocations served from the cache across all runs. Under tinymold, cargo runs with RUSTC_WRAPPER=tinymold, the wall time includes tinymold fresh restore before and fresh record after every build, and the worktree and branch-switch cases include restoring a target/ snapshot.
| Case | baseline | sccache | tinymold |
|---|---|---|---|
| Clean build | 3.35s · link 0.36s · 33 compiled | 1.89s · link 0.31s · 33 compiled · 64/96 hits · first (cold) run 4.09s | 1.09s · link 0.00s · 33 compiled · 78/120 hits · first (cold) run 4.34s |
| No-op rebuild | 0.06s · link 0.00s · 0 compiled | 0.03s · link 0.00s · 0 compiled | 0.05s · link 0.00s · 0 compiled |
| Comment edit | 0.53s · link 0.06s · 1 compiled | 0.90s · link 0.05s · 1 compiled | 0.55s · link 0.07s · 1 compiled · 0/3 hits |
| Private function body edit | 0.59s · link 0.06s · 1 compiled | 0.93s · link 0.05s · 1 compiled | 0.60s · link 0.06s · 1 compiled · 0/3 hits |
| Public signature edit | 0.59s · link 0.07s · 4 compiled | 1.23s · link 0.05s · 4 compiled · 0/9 hits | 0.62s · link 0.07s · 4 compiled · 0/12 hits |
| Second worktree, same commit | 3.39s · link 0.36s · 33 compiled | 1.86s · link 0.31s · 33 compiled · 88/96 hits | 0.08s · link 0.00s · 0 compiled |
| Every source file touched, no content change | 0.84s · link 0.10s · 9 compiled | 1.00s · link 0.09s · 9 compiled · 24/24 hits | 0.04s · link 0.00s · 0 compiled |
| Branch switch A→B→A | 1.18s · link 0.13s · 8 compiled | 2.19s · link 0.11s · 8 compiled · 9/18 hits | 0.43s · link 0.00s · 8 compiled · 24/24 hits |
| cargo test --no-run | 2.82s · link 0.16s · 6 compiled | 2.16s · link 0.16s · 6 compiled · 8/12 hits | 0.29s · link 0.00s · 6 compiled · 16/27 hits |
target/ by kind (baseline):
| After | rlib | rmeta | debuginfo | objects | incremental | executables | other | total |
|---|---|---|---|---|---|---|---|---|
| Clean build | 82 MiB | 27 MiB | 0 MiB | 73 MiB | 100 MiB | 36 MiB | 0 MiB | 318 MiB |
| cargo test --no-run | 95 MiB | 29 MiB | 0 MiB | 105 MiB | 156 MiB | 49 MiB | 0 MiB | 434 MiB |
| Case | baseline | sccache | tinymold |
|---|---|---|---|
| Clean build | 4.22s · link 0.34s · 32 compiled | 2.07s · link 0.33s · 32 compiled · 66/99 hits · first (cold) run 5.32s | 1.25s · link 0.00s · 32 compiled · 78/123 hits · first (cold) run 4.21s |
| No-op rebuild | 0.07s · link 0.00s · 0 compiled | 0.04s · link 0.00s · 0 compiled | 0.04s · link 0.00s · 0 compiled |
| Comment edit | 0.17s · link 0.04s · 1 compiled | 0.16s · link 0.04s · 1 compiled | 0.15s · link 0.05s · 1 compiled · 0/3 hits |
| Private function body edit | 0.34s · link 0.04s · 1 compiled | 0.51s · link 0.04s · 1 compiled · 0/3 hits | 0.34s · link 0.04s · 1 compiled · 0/6 hits |
| Public signature edit | 0.61s · link 0.05s · 5 compiled | 1.03s · link 0.04s · 5 compiled · 0/15 hits | 0.60s · link 0.04s · 5 compiled · 0/18 hits |
| Second worktree, same commit | 4.17s · link 0.36s · 32 compiled | 2.03s · link 0.29s · 32 compiled · 92/99 hits | 0.07s · link 0.00s · 0 compiled |
| Every source file touched, no content change | 0.60s · link 0.05s · 5 compiled | 0.35s · link 0.04s · 5 compiled · 15/15 hits | 0.04s · link 0.00s · 0 compiled |
| Branch switch A→B→A | 1.32s · link 0.10s · 10 compiled | 0.71s · link 0.08s · 10 compiled · 30/30 hits | 0.49s · link 0.00s · 10 compiled · 36/36 hits |
| cargo test --no-run | 2.75s · link 0.40s · 9 compiled | 1.50s · link 0.35s · 9 compiled · 16/24 hits | 0.55s · link 0.00s · 9 compiled · 32/48 hits |
target/ by kind (baseline):
| After | rlib | rmeta | debuginfo | objects | incremental | executables | other | total |
|---|---|---|---|---|---|---|---|---|
| Clean build | 54 MiB | 8 MiB | 0 MiB | 54 MiB | 32 MiB | 28 MiB | 0 MiB | 175 MiB |
| cargo test --no-run | 62 MiB | 14 MiB | 0 MiB | 66 MiB | 55 MiB | 43 MiB | 0 MiB | 240 MiB |
| Case | baseline | sccache | tinymold |
|---|---|---|---|
| Clean build | 7.34s · link 1.47s · 109 compiled | 11.13s · link 1.41s · 109 compiled · 698/1047 hits · first (cold) run 11.69s | 5.27s · link 0.09s · 109 compiled · 259/393 hits · first (cold) run 14.33s |
| No-op rebuild | 0.10s · link 0.00s · 0 compiled | 0.06s · link 0.00s · 0 compiled | 0.09s · link 0.00s · 0 compiled |
| Comment edit | 0.85s · link 0.08s · 1 compiled | 0.68s · link 0.09s · 1 compiled · 3/3 hits | 0.70s · link 0.09s · 1 compiled · 3/6 hits |
| Private function body edit | 0.86s · link 0.08s · 1 compiled | 0.66s · link 0.07s · 1 compiled · 3/3 hits | 0.69s · link 0.09s · 1 compiled · 3/6 hits |
| Public signature edit | 0.88s · link 0.08s · 1 compiled | 1.16s · link 0.07s · 1 compiled · 0/3 hits | 0.88s · link 0.07s · 1 compiled · 0/6 hits |
| Second worktree, same commit | 7.17s · link 1.39s · 109 compiled | 4.50s · link 1.28s · 109 compiled · 823/1047 hits | 1.55s · link 0.08s · 1 compiled · 0/3 hits |
| Every source file touched, no content change | 0.99s · link 0.11s · 1 compiled | 0.75s · link 0.10s · 1 compiled · 3/3 hits | 0.09s · link 0.00s · 0 compiled |
| Branch switch A→B→A | 1.72s · link 0.14s · 2 compiled | 1.80s · link 0.13s · 2 compiled · 3/6 hits | 1.44s · link 0.17s · 2 compiled · 6/12 hits |
| cargo test --no-run | 2.25s · link 2.66s · 29 compiled | 1.78s · link 2.47s · 29 compiled · 54/81 hits | 0.71s · link 0.00s · 29 compiled · 94/141 hits |
target/ by kind (baseline):
| After | rlib | rmeta | debuginfo | objects | incremental | executables | other | total |
|---|---|---|---|---|---|---|---|---|
| Clean build | 199 MiB | 61 MiB | 0 MiB | 155 MiB | 46 MiB | 82 MiB | 9 MiB | 552 MiB |
| cargo test --no-run | 220 MiB | 69 MiB | 0 MiB | 183 MiB | 91 MiB | 225 MiB | 9 MiB | 797 MiB |
A/B: baseline vs cgu-128 (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 7 runs, 2026-10-14)
cgu-128: profile.dev.codegen-units=128. Each edit is built by both sides back to back, in alternating order, each in its own target directory. Cells: median wall time (min–max).
| Project | Edit | baseline | cgu-128 | change |
|---|---|---|---|---|
| ripgrep 14.1.1 | Comment edit | 0.639s (0.63–1.05) | 0.529s (0.47–0.54) | -17% |
| ripgrep 14.1.1 | Private function body edit | 0.674s (0.65–1.09) | 0.625s (0.56–3.71) | -7% |
| ripgrep 14.1.1 | Public signature edit | 0.869s (0.81–4.55) | 1.058s (0.57–3.93) | +22% |
| bat v0.24.0 | Comment edit | 1.015s (0.93–1.09) | 0.767s (0.72–0.85) | -24% |
| bat v0.24.0 | Private function body edit | 1.013s (0.95–1.10) | 0.802s (0.78–0.83) | -21% |
| bat v0.24.0 | Public signature edit | 1.057s (1.02–1.13) | 0.850s (0.82–0.88) | -20% |
A/B: baseline vs cgu-32 (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 7 runs, 2026-10-14)
cgu-32: profile.dev.codegen-units=32. Each edit is built by both sides back to back, in alternating order, each in its own target directory. Cells: median wall time (min–max).
| Project | Edit | baseline | cgu-32 | change |
|---|---|---|---|---|
| ripgrep 14.1.1 | Comment edit | 0.579s (0.56–0.72) | 0.451s (0.42–1.21) | -22% |
| ripgrep 14.1.1 | Private function body edit | 0.620s (0.59–1.02) | 0.711s (0.68–0.81) | +15% |
| ripgrep 14.1.1 | Public signature edit | 0.712s (0.69–0.76) | 0.487s (0.47–0.59) | -32% |
| bat v0.24.0 | Comment edit | 0.878s (0.84–1.00) | 0.607s (0.59–0.65) | -31% |
| bat v0.24.0 | Private function body edit | 0.903s (0.86–0.95) | 0.663s (0.64–0.70) | -27% |
| bat v0.24.0 | Public signature edit | 0.941s (0.91–1.02) | 0.678s (0.67–0.71) | -28% |
A/B: baseline vs cgu-64 (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 7 runs, 2026-10-15)
cgu-64: profile.dev.codegen-units=64. Each edit is built by both sides back to back, in alternating order, each in its own target directory. Cells: median wall time (min–max).
| Project | Edit | baseline | cgu-64 | change |
|---|---|---|---|---|
| ripgrep 14.1.1 | Comment edit | 0.598s (0.57–0.64) | 0.512s (0.44–0.69) | -14% |
| ripgrep 14.1.1 | Private function body edit | 0.641s (0.61–0.66) | 0.558s (0.51–0.58) | -13% |
| ripgrep 14.1.1 | Public signature edit | 0.640s (0.61–0.69) | 0.507s (0.49–0.56) | -21% |
| ripgrep 14.1.1 | Clean build | 3.969s (3.85–9.14) | 3.723s (3.55–6.02) | -6% |
| bat v0.24.0 | Comment edit | 0.807s (0.77–1.07) | 0.680s (0.64–1.35) | -16% |
| bat v0.24.0 | Private function body edit | 0.830s (0.82–0.86) | 0.688s (0.67–0.81) | -17% |
| bat v0.24.0 | Public signature edit | 0.862s (0.85–1.07) | 0.736s (0.68–0.81) | -15% |
| bat v0.24.0 | Clean build | 7.835s (7.62–19.52) | 8.006s (7.72–13.47) | +2% |
| tinymold 3574dd6 | Comment edit | 0.166s (0.16–0.18) | 0.172s (0.17–0.18) | +4% |
| tinymold 3574dd6 | Private function body edit | 0.327s (0.31–0.35) | 0.282s (0.27–0.30) | -14% |
| tinymold 3574dd6 | Public signature edit | 0.631s (0.57–0.78) | 0.548s (0.47–0.64) | -13% |
Edit-loop experiments (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 5 runs, 2026-10-14)
Each variant is layered on the baseline. Cells: median wall time, the change against the reference (the stable baseline, or baseline+nightly for nightly variants), and median link time.
| Variant | Settings |
|---|---|
| cgu-16 | profile.dev.codegen-units=16 |
| cgu-32 | profile.dev.codegen-units=32 |
| cgu-64 | profile.dev.codegen-units=64 |
| cranelift | RUSTFLAGS -Zcodegen-backend=cranelift; toolchain nightly |
| deps-nodebug | profile.dev.package."*".debug=false |
| line-tables | profile.dev.debug="line-tables-only"; profile.dev.package."*".debug=false |
| nightly | toolchain nightly |
| nodebug | profile.dev.debug=false; profile.dev.package."*".debug=false |
| threads-8 | RUSTFLAGS -Zthreads=8; toolchain nightly |
| Case | baseline | baseline+deps-nodebug | baseline+line-tables | baseline+nodebug | baseline+cgu-16 | baseline+nightly | baseline+threads-8 | baseline+cranelift | baseline+cgu-32 | baseline+cgu-64 |
|---|---|---|---|---|---|---|---|---|---|---|
| No-op rebuild | 0.06s · link 0.00s | 0.07s (+8%) · link 0.00s | 0.07s (+3%) · link 0.00s | 0.06s (-1%) · link 0.00s | 0.07s (+5%) · link 0.00s | 0.07s (+3%) · link 0.00s | 0.07s (-0%) · link 0.00s | 0.07s (+3%) · link 0.00s | – | – |
| Comment edit | 0.66s · link 0.06s | 0.61s (-8%) · link 0.06s | 0.56s (-15%) · link 0.06s | 0.62s (-6%) · link 0.06s | 0.45s (-32%) · link 0.06s | 0.62s (-6%) · link 0.06s | 0.61s (-2%) · link 0.06s | 0.64s (+3%) · link 0.06s | 0.48s (-28%) · link 0.06s | 0.56s (-16%) · link 0.06s |
| Private function body edit | 0.73s · link 0.06s | 0.62s (-14%) · link 0.06s | 0.60s (-18%) · link 0.06s | 0.65s (-10%) · link 0.06s | 0.88s (+20%) · link 0.05s | 0.64s (-12%) · link 0.06s | 0.65s (+2%) · link 0.06s | 0.64s (-0%) · link 0.06s | 0.80s (+10%) · link 0.06s | 0.57s (-22%) · link 0.06s |
| Public signature edit | 0.96s · link 0.06s | 0.63s (-35%) · link 0.06s | 0.66s (-31%) · link 0.06s | 0.69s (-29%) · link 0.06s | 0.47s (-51%) · link 0.05s | 0.70s (-27%) · link 0.06s | 0.74s (+6%) · link 0.06s | 0.76s (+9%) · link 0.06s | 0.58s (-39%) · link 0.06s | 0.66s (-31%) · link 0.06s |
| Case | baseline | baseline+deps-nodebug | baseline+line-tables | baseline+nodebug | baseline+cgu-16 | baseline+nightly | baseline+threads-8 | baseline+cgu-32 | baseline+cgu-64 |
|---|---|---|---|---|---|---|---|---|---|
| No-op rebuild | 0.10s · link 0.00s | 0.10s (+4%) · link 0.00s | 0.10s (+5%) · link 0.00s | 0.11s (+11%) · link 0.00s | 0.09s (-5%) · link 0.00s | 0.10s (+0%) · link 0.00s | 0.11s (+14%) · link 0.00s | – | – |
| Comment edit | 1.18s · link 0.07s | 1.04s (-11%) · link 0.07s | 1.06s (-10%) · link 0.07s | 1.23s (+4%) · link 0.08s | 0.72s (-39%) · link 0.07s | 0.97s (-18%) · link 0.07s | 1.06s (+9%) · link 0.08s | 1.09s (-8%) · link 0.08s | 1.02s (-13%) · link 0.09s |
| Private function body edit | 1.28s · link 0.08s | 1.08s (-16%) · link 0.07s | 1.11s (-13%) · link 0.07s | 1.06s (-17%) · link 0.07s | 0.79s (-39%) · link 0.07s | 1.03s (-20%) · link 0.07s | 1.06s (+3%) · link 0.07s | 1.18s (-8%) · link 0.09s | 1.01s (-21%) · link 0.09s |
| Public signature edit | 1.28s · link 0.08s | 1.09s (-15%) · link 0.07s | 1.14s (-11%) · link 0.07s | 0.98s (-23%) · link 0.07s | 0.81s (-37%) · link 0.07s | 1.16s (-9%) · link 0.07s | 1.16s (+0%) · link 0.07s | 1.09s (-15%) · link 0.08s | 1.02s (-20%) · link 0.08s |
| Case | baseline | baseline+deps-nodebug | baseline+line-tables | baseline+nodebug | baseline+cgu-16 | baseline+nightly | baseline+threads-8 | baseline+cranelift | baseline+cgu-32 | baseline+cgu-64 |
|---|---|---|---|---|---|---|---|---|---|---|
| No-op rebuild | 0.06s · link 0.00s | 0.06s (+2%) · link 0.00s | 0.06s (+3%) · link 0.00s | 0.07s (+15%) · link 0.00s | 0.06s (-2%) · link 0.00s | 0.07s (+3%) · link 0.00s | 0.07s (+2%) · link 0.00s | 0.07s (+2%) · link 0.00s | – | – |
| Comment edit | 0.18s · link 0.05s | 0.17s (-6%) · link 0.04s | 0.17s (-2%) · link 0.04s | 0.20s (+12%) · link 0.04s | 0.16s (-8%) · link 0.04s | 0.18s (+2%) · link 0.04s | 0.20s (+9%) · link 0.04s | 0.19s (+7%) · link 0.04s | 0.18s (+4%) · link 0.05s | 0.18s (+2%) · link 0.04s |
| Private function body edit | 0.36s · link 0.04s | 0.37s (+2%) · link 0.05s | 0.36s (-1%) · link 0.04s | 0.39s (+8%) · link 0.04s | 0.40s (+10%) · link 0.04s | 0.40s (+10%) · link 0.04s | 0.42s (+5%) · link 0.04s | 0.40s (+1%) · link 0.04s | 0.39s (+8%) · link 0.05s | 0.30s (-16%) · link 0.04s |
| Public signature edit | 0.63s · link 0.05s | 0.61s (-3%) · link 0.04s | 0.65s (+3%) · link 0.04s | 0.66s (+5%) · link 0.04s | 0.44s (-29%) · link 0.04s | 0.72s (+15%) · link 0.04s | 0.80s (+11%) · link 0.04s | 0.81s (+12%) · link 0.04s | 0.50s (-20%) · link 0.05s | 0.55s (-12%) · link 0.05s |
Baseline: plain cargo, default linker (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 5 runs, 2026-10-14)
What every tinymold phase is measured against. Times are wall clock; loops report the median.
| Scenario | ripgrep 14.1.1 | bat v0.24.0 |
|---|---|---|
| Clean build | 3.5s | 21.6s |
| No-op rebuild | 0.07s | 0.10s |
| One-line edit → rebuilt binary | 0.52s | 0.75s |
| Every source file touched, no content change | 0.84s | 0.91s |
| Build tests (after the binary) | 3.3s | 4.6s |
| Second worktree, same commit, clean build | 3.8s | 13.2s |
target/ after binary + tests |
825 MiB | 953 MiB |
Second worktree deps/ artifacts |
195 MiB | 434 MiB |
| …of which byte-identical to the first worktree's | 120 MiB (61%) | 360 MiB (83%) |
Clean builds: no cache, dry run, cached run (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 3 runs, 2026-10-14)
Cache: sccache 0.17.0, private directory and port. Each cell is the median clean build; hits and misses count rustc invocations.
| Scenario | ripgrep 14.1.1 | bat v0.24.0 |
|---|---|---|
| No cache | 3.3s | 7.5s |
| Empty cache (dry run, fills it) | 4.2s | 11.2s |
| Warm cache (cached run) | 1.9s | 6.5s |
| Warm cache, second worktree at another path | 1.8s | 8.9s |
| Cached run hit rate | 32/32 hits | 349/349 hits |
| Second worktree hit rate | 32/32 hits | 349/349 hits |
| Not cacheable (bins, proc-macros, incremental, …) | 0 | 0 |
| Cache size on disk | 36 MiB | 128 MiB |
cargo test --no-run --locked --package <package> on a fresh checkout at the same path, with nothing on disk but what the job restores. Each cell is the median of restore plus build; downloading an archive from GitHub's cache is not included, so archive sizes are listed for that estimate.
| Mode | ripgrep 14.1.1 | bat v0.24.0 | tinymold f3767e1 |
|---|---|---|---|
| Cold, no cache | 3.9s | 9.9s | 7.6s |
target/ restored, sources touched |
1.8s | 3.3s | 3.3s |
| tinymold store restored (GitHub backend) | 1.9s (47/49 hits) | 6.7s (177/178 hits) | 3.8s (129/132 hits) |
| tinymold from bazel-remote only (remote backend) | 1.4s (47/49 hits) | 7.1s (175/178 hits) | 2.9s (129/132 hits) |
target/ archive |
259 MiB | 698 MiB | 559 MiB |
| tinymold store archive | 241 MiB | 592 MiB | 527 MiB |
Cache hits on many-output units (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 7 runs, 2026-10-15)
Every source file touched, then rebuilt through the wrapper, so each workspace unit is a cache hit. Incremental units with profile.dev.split-debuginfo="unpacked" keep one object file per codegen unit, so a hit places hundreds of files. Before is the --against revision, after is the checkout's; they take alternate turns on the same store. Restore times come from TINYMOLD_TRACE; cells are medians (min–max). A crate's row adds up all of its units, such as a library and a binary of the same name.
| Project | What | before | after | change |
|---|---|---|---|---|
| ripgrep 14.1.1 tinymold 185d6ba → 7312d99 |
Build wall time | 0.26s (0.24–0.33) | 0.18s (0.16–0.20) | -30% |
| ripgrep 14.1.1 tinymold 185d6ba → 7312d99 |
All 10 restores, summed | 315 ms (300–365) | 31 ms (28–42) | -90% |
| ripgrep 14.1.1 tinymold 185d6ba → 7312d99 |
rg restore (258 files) |
43.6 ms (41.3–59.4) | 3.4 ms (3.2–4.3) | -92% |
| ripgrep 14.1.1 tinymold 185d6ba → 7312d99 |
ignore restore (212 files) |
70.0 ms (64.6–86.0) | 5.8 ms (4.5–6.6) | -92% |
| ripgrep 14.1.1 tinymold 185d6ba → 7312d99 |
globset restore (132 files) |
47.6 ms (44.5–54.5) | 2.9 ms (2.6–7.2) | -94% |
| bat v0.24.0 tinymold 185d6ba → 7312d99 |
Build wall time | 0.60s (0.57–0.75) | 0.59s (0.53–0.63) | -1% |
| bat v0.24.0 tinymold 185d6ba → 7312d99 |
All 2 restores, summed | 44 ms (44–55) | 7 ms (7–8) | -84% |
| bat v0.24.0 tinymold 185d6ba → 7312d99 |
bat restore (1581 files) |
43.8 ms (43.2–54.3) | 6.6 ms (6.3–7.1) | -85% |
| bat v0.24.0 tinymold 185d6ba → 7312d99 |
build_script_build restore (0 files) |
0.5 ms (0.5–0.7) | 0.5 ms (0.5–0.5) | +0% |
| tinymold 7312d99 tinymold 185d6ba → 7312d99 |
Build wall time | 0.22s (0.21–0.30) | 0.12s (0.10–0.14) | -48% |
| tinymold 7312d99 tinymold 185d6ba → 7312d99 |
All 6 restores, summed | 152 ms (149–175) | 30 ms (28–50) | -80% |
| tinymold 7312d99 tinymold 185d6ba → 7312d99 |
tinymold restore (211 files) |
36.4 ms (34.9–38.0) | 6.7 ms (6.1–8.2) | -82% |
| tinymold 7312d99 tinymold 185d6ba → 7312d99 |
tinymold_remote restore (191 files) |
33.1 ms (31.8–33.7) | 5.4 ms (5.1–5.6) | -84% |
| tinymold 7312d99 tinymold 185d6ba → 7312d99 |
tinymold_wrap restore (169 files) |
28.8 ms (27.9–30.1) | 5.1 ms (4.7–5.6) | -82% |
Running tests again (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 3/5 runs by row, 2026-10-15)
cargo test for the project's package, built through RUSTC_WRAPPER=tinymold on both sides; only the runner differs. again: nothing changed. private_body: a one-line private edit before every run. Cells are median wall time (min–max) and test binaries replayed / run by tinymold run-test; runs is how many measurements each side took.
Settings: cargo build jobs: 18; target filesystem: apfs; rustc wrapper: tinymold on both sides, one fresh store per project; cargo runner: cargo side: none, any CARGO_TARGET_RUNNER removed; run-test side: CARGO_TARGET_RUNNER="tinymold run-test"; linker: cc, rustc's default, with no timing wrapper; incremental: on (CARGO_INCREMENTAL=1); tmpdir: one per project, emptied before every measurement; doctests: run on both sides; rustdoc does not go through the runner.
| Project | Case | runs | cargo | run-test | replayed / ran | change |
|---|---|---|---|---|---|---|
| ripgrep 14.1.1 | again | 5 | 1.29s (1.03–1.60) | 0.09s (0.07–0.11) | 2 / 0 | -93% |
| ripgrep 14.1.1 | private_body | 5 | 5.39s (4.82–6.21) | 4.68s (4.61–6.76) | 0 / 2 | -13% |
| tinymold bb944ae | again | 3 | 18.09s (11.83–18.27) | 1.97s (1.53–4.74) | 5 / 0 | -89% |
| tinymold bb944ae | private_body | 3 | 15.45s (15.38–21.73) | 20.69s (13.96–21.17) | 0 / 5 | +34% |
OpenHuman openhuman-core in Docker (linux aarch64, 12 CPUs, 16 GiB, rustc 1.96.1 (31fca3adb 2026-06-26), 2026-10-15)
OpenHuman 9a0a654, dev profile, -j 12, sources and store on ext4 in a Docker volume (mold 3.0.0, GNU ld (GNU Binutils for Debian) 2.40, sccache 0.18.0; host: darwin arm64, 18 CPUs). Each cell: median wall time of 3 runs (one for a cold build), link time, units compiled, and cache hits (summed over runs). ld and mold have no cache, so their warm and second-checkout builds would be their cold build. A second checkout's repeat runs reuse what its first run stored at that path, so the first run is shown when it differs.
| Case | ld | mold | tinymold | sccache | sccache-noinc |
|---|---|---|---|---|---|
| Clean build, empty cache | 167.2s · link 22.06s · 421 compiled | 175.2s · link 3.16s · 421 compiled | 176.0s · link 4.06s · 421 compiled · 0/474 hits | 200.3s · link 8.59s · 421 compiled · 0/378 hits | 195.9s · link 6.52s · 421 compiled · 0/430 hits |
| No-op rebuild | 0.2s · link 0.00s · 0 compiled | 0.3s · link 0.00s · 0 compiled | 0.2s · link 0.00s · 0 compiled | 0.3s · link 0.00s · 0 compiled | 0.3s · link 0.00s · 0 compiled |
| Edit the binary's main.rs | 15.4s · link 14.73s · 1 compiled | 1.1s · link 0.61s · 1 compiled | 2.1s · link 0.63s · 1 compiled · 0/3 hits | 1.6s · link 0.96s · 1 compiled | 1.1s · link 0.59s · 1 compiled |
| Edit the core library's lib.rs | 32.9s · link 16.49s · 5 compiled | 16.4s · link 0.67s · 5 compiled | 19.1s · link 0.00s · 5 compiled · 12/15 hits | 19.5s · link 1.34s · 5 compiled | 138.9s · link 0.68s · 5 compiled · 6/12 hits |
| Clean target/, warm cache | - | - | 4.5s · link 0.00s · 421 compiled · 1410/1422 hits | 185.5s · link 3.18s · 421 compiled · 1134/1134 hits | 13.4s · link 2.68s · 421 compiled · 1290/1290 hits |
| Second checkout at another path | - | - | 5.8s · link 0.00s · 421 compiled · 1287/1422 hits · first run 165.1s | 213.3s · link 4.46s · 421 compiled · 1065/1134 hits | 13.3s · link 3.11s · 421 compiled · 1169/1290 hits · first run 187.4s |
| Second checkout from a target/ snapshot | - | - | 4.3s · link 0.00s · 6 compiled · 12/18 hits · restore 6.4s · first run 155.3s | - | - |
tinymold+mold against plain mold, by median wall time plus any snapshot restore; cached cases compare against the cold mold build: clean build, empty cache: 1.0x; no-op rebuild: 1.1x; edit the binary's main.rs: 0.5x; edit the core library's lib.rs: 0.9x; clean target/, warm cache: 38.8x; second checkout at another path: 30.2x (first run at a new path 1.1x); second checkout from a target/ snapshot: 16.3x (first run at a new path 1.1x).
sccache+mold against plain mold, by median wall time plus any snapshot restore; cached cases compare against the cold mold build: clean build, empty cache: 0.9x; no-op rebuild: 1.0x; edit the binary's main.rs: 0.7x; edit the core library's lib.rs: 0.8x; clean target/, warm cache: 0.9x; second checkout at another path: 0.8x.
sccache-noinc+mold against plain mold, by median wall time plus any snapshot restore; cached cases compare against the cold mold build: clean build, empty cache: 0.9x; no-op rebuild: 0.9x; edit the binary's main.rs: 1.0x; edit the core library's lib.rs: 0.1x; clean target/, warm cache: 13.1x; second checkout at another path: 13.2x (first run at a new path 0.9x).
Link time, GNU ld over mold: clean build, empty cache: 7.0x; edit the binary's main.rs: 24.3x; edit the core library's lib.rs: 24.8x.
Dedup probe: second worktree vs first (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 1 runs, 2026-10-14)
Share of the second worktree's target/ bytes that are byte-identical to something in the first's, at three granularities. Incremental state is excluded.
| Project | Files | rlib members | DWARF sections |
|---|---|---|---|
| tinymold 9db903e | 79% of 116 MiB | 92% of 43 MiB | 92% of 30 MiB |
| Project | Linker | Edit → rebuilt binary (median) | min–max | Full build | Binary | target/ |
|---|---|---|---|---|---|---|
| ripgrep 14.1.1 | default | 0.91s | 0.87–0.98s | 9.3s | 15 MiB | 451 MiB |
| ripgrep 14.1.1 | rust-lld | 0.94s | 0.89–1.12s | 9.0s | 14 MiB | 450 MiB |
| Project | Linker | Edit → rebuilt binary (median) | min–max | Full build | Binary | target/ |
|---|---|---|---|---|---|---|
| bat v0.24.0 | default | 1.07s | 0.84–5.51s | 20.5s | 19 MiB | 693 MiB |
| bat v0.24.0 | rust-lld | 3.50s | 2.57–3.72s | 29.8s | 18 MiB | 689 MiB |
Restoring a 512 MiB artifact (darwin arm64, 18 CPUs, rustc 1.99.0 (b940084d7 2026-09-28), 5 runs, 2026-10-14)
| Method | Median | min–max |
|---|---|---|
| python byte copy (baseline) | 221.0 ms | 214.4–232.3 ms |
| tinymold cas get --strategy reflink | 2.8 ms | 2.4–5.8 ms |
| tinymold cas get --strategy hardlink | 2.9 ms | 2.7–3.1 ms |
| tinymold cas get --strategy copy | 325.6 ms | 311.6–443.3 ms |
Medians over runs; tinymold serve: release build, tinymold-4bd43ab52852, uncompressed store; bazel-remote: native, github.com/buchgr/bazel-remote/v2 v2.6.2, default zstd storage. Small hits are 2000 GETs per run; cold start opens a store of 20000 action results. Not measured: FindMissingBlobs (gRPC).
| Case | tinymold serve | bazel-remote |
|---|---|---|
| hit 1 KiB p50 (ms) | 0.1622 | 0.1357 |
| hit 1 KiB p99 (ms) | 0.3102 | 0.3794 |
| download 4 MiB (MB/s) | 1372.47 | 982.486 |
| upload 4 MiB (MB/s) | 461.219 | 348.421 |
| cold start (s) | 0.0171 | 0.0674 |
Cumulative edits with build, clippy, tests and coverage after each, per mode and linker: see EDITS.md.
Requirements: a Rust toolchain, git, and Python 3.9 or newer. No Python
packages are needed.
python3 bench/bench.py scenarios --runs 3 # clones the corpus into bench/.work/
python3 bench/bench.py scenarios --project ripgrep --modes baseline --cases clean,comment
python3 bench/bench.py scenarios --project ripgrep --modes baseline \
--variants line-tables,cgu-64 --cases comment,private_body # edit-loop experiments
python3 bench/bench.py ab --variant cgu-64 --runs 7 # interleaved A/B
python3 bench/bench.py dedup
python3 bench/bench.py baseline --runs 5
python3 bench/bench.py cache --runs 3 # needs sccache on PATH
python3 bench/bench.py link --runs 5
python3 bench/bench.py cas --runs 5 --size-mb 512
python3 bench/report.py # refresh the tables and chartsTo see where one build's time goes rather than how long it takes (critical
path, idle job slots, build scripts, links, wrapper steps), use
bench/profile_build.py; see PROFILING.md. For a large
real project, DOCKER.md builds OpenHuman on Linux in Docker with
GNU ld, mold, tinymold, and sccache, cold, warm, and in the edit loop.
Use ab for any claim about a setting. scenarios --variants runs each side
to completion before the next, so load drift on a shared machine biases it;
ab builds every edit with both sides back to back, in alternating order.
Every corpus build runs as stock cargo. Cargo reads config.toml from
CARGO_HOME and every ancestor directory, so a developer's own tuning (a
rustc-wrapper such as sccache, reduced debuginfo, a jobs limit, incremental
off) would otherwise leak into the numbers. The harness disables any rustc
wrapper, turns incremental on, forces full debuginfo, sets jobs to the CPU
count through --config (which outranks every config file), and records
those settings in each result. The cache benchmark and the sccache mode
then add sccache back explicitly, on a private port and directory. sccache
refuses to run while CARGO_INCREMENTAL=1 is set, so under sccache workspace
crates build with cargo's default incremental setting and are reported as not
cacheable.
The scenarios benchmark also pins and records the filesystem type of the
build directory, the linker and its thread setting, and output reuse.
Single-project runs (--project) replace that project's rows in the results
file and keep the rest.
link measures every linker it finds: the default, the rust-lld bundled with
the toolchain, and on Linux mold and wild when they're on PATH. Install
those to include them. Each linker builds into its own directory under the
corpus project's target/, so linkers never invalidate each other's
artifacts. The first build per linker is a full build (reported as "Full
build"); the edit loop is timed after it.
Commit the JSON files from bench/results/ along with the regenerated
README.md and SVG charts. The result file name carries the OS and
architecture, so results from different machines sit side by side.
corpus.json pins each project to an exact tag:
| Project | Revision | Role | Public-signature edit |
|---|---|---|---|
| ripgrep | 14.1.1 | small, link-bound binary | crates/searcher/src/lib.rs |
| bat | v0.24.0 | build scripts and C dependencies | src/lib.rs |
| tinymold | this repository's HEAD |
small workspace | crates/tinymold-hash/src/lib.rs |
| tokio | tokio-1.53.3 | test-heavy library, 173 integration test files | tokio/src/lib.rs |
| Bevy | v0.19.1 | deep graph, link-bound 3d_scene example |
crates/bevy_utils/src/lib.rs |
Each project names a file and anchor for every edit kind in corpus.json.
A comment edit appends a comment. A private body edit inserts
std::hint::black_box(n); after a function's opening line, so the crate
recompiles but its interface doesn't change. A public signature edit adds a
pub fn to a library that other crates depend on, so every dependent rebuilds.
Phase 1 of the roadmap may still add a
-sys-heavy workspace. Add a project by appending to corpus.json with a tag
or commit, the package and what to build (bin, example, or a library with
optional features), and an edits entry for each edit kind.
- Wall clock, median of N. Each sample is one
cargo buildprocess, timed end to end withtime.perf_counter. The min and max are reported so noisy runs are visible. - The edit is a comment. Appending a comment changes spans, so rustc recompiles the edited crate and cargo relinks the binary. This is the smallest edit a developer makes, which isolates the fixed cost of a rebuild.
- Everything that moves the result is recorded. OS, kernel release,
architecture, CPU count, and
rustc -Vgo into every result file. Linker benchmarks are notoriously sensitive: in a dispute between the wild and mold authors, filesystem, output-file reuse, fork mode, and thread count alone moved one ratio from 1.0x to 0.6x (research). The Phase 1 harness pins those too. casincludes process start-up. Eachtinymold cas getsample is a full process launch, which is what a build tool would pay per artifact today. The hot cache server in Phase 6 removes that cost.- No cherry-picking. Every run's samples are stored in the JSON files.
These are other people's measurements, kept here for context. tinymold has not reproduced them.
| Linking debug builds on a Threadripper 7980X (64 cores) | lld | wild | mold |
|---|---|---|---|
| Chromium 145 (4.51 GiB output) | 16.64 s | 3.98 s | 1.65 s |
| Clang 21 (4.19 GiB) | 6.19 s | 3.71 s | 1.34 s |
| ClickHouse 26.1 (5.58 GiB) | 6.62 s | 4.40 s | 0.98 s |
| Blender 5.2 (2.46 GiB) | 4.72 s | 1.98 s | 0.86 s |
Source: the mold README, August 2026, medians of 3 runs. The Rust project reports that switching ripgrep's debug build to lld makes incremental rebuilds about 40% faster end to end (Rust blog).