How to Reduce Rust Compile Time [2026] (sccache + mold)
A measurable 2026 playbook to reduce Rust compile time: profile with Cargo timings, fix the build graph, get real sccache hit rates, and cut link time with mold.
How to Reduce Rust Compile Time [2026] (sccache + mold)
If you take one thing from this post, let it be this: you can reduce Rust compile time without cargo-culting flags. You need a harness. A few numbers you can trust. Then you change one variable, re-run, and decide if it was worth the churn.

You’ll finish this guide with a repeatable setup that tells you, in minutes, whether you actually reduced compile time. Not “it feels faster”. Real numbers: cold build time, warm build time, incremental rebuild time after a one-line edit, sccache hit rate, and linker time.
If you run a Rust monorepo in 2026, compile time isn’t a “developer experience” nit. It’s a CI bill. It’s also a throughput cap. When builds are slow, everything gets worse: smaller refactors stop happening, reviews batch up, and people start treating the build as an unpredictable weather system.
Here’s my stance: stop chasing random Cargo knobs. Treat compile time like observability. Measure the build graph, then change one thing at a time.
Before we start: the measurement mindset here is the same one I use for cost and performance work on this site. I maintain a live LLM pricing tracker at /llm-prices, and the main lesson from that project is that _numbers without assumptions are propaganda_. Build time “improvements” are the same. If you don’t separate cold vs warm vs incremental, you’re lying to yourself.
What is Rust compile time optimization?
Rust compile time optimization is the set of techniques that reduce the elapsed time from cargo build to a usable binary by improving build graph parallelism, avoiding unnecessary recompilation via incremental builds and caching, and shrinking expensive compiler and linker work.

In practice, you’re optimizing three things:
- The crate dependency graph (parallelism and invalidation boundaries)
- Reuse (incremental compilation + local/remote compilation cache)
- Linking (often the last big serial step)
A measurable harness to reduce Rust compile time
You can’t optimize something you can’t re-run. The fastest way to waste a week is to “try a bunch of stuff” and then argue in Slack about whether it helped.

So I always start with a tiny benchmark script and a rule: same target, same features, same toolchain, same environment. Every time.
The 5-metric baseline
Run these and record them in your PR description:
- Cold build: clean build from scratch.
- Warm build: immediate rebuild with no changes.
- Incremental rebuild: rebuild after a one-line edit in a leaf crate.
- `sccache` hit rate: hits / requests.
- Link time: seconds spent in linking.
Concrete targets that usually matter in real teams:
- Cold build down by 20–50% is “worth telling the org”.
- Warm build under 5s is where iteration stops feeling sticky.
- Incremental rebuild under 1–2s for leaf edits is the happy place.
sccachehit rate above 70% in CI is where it starts paying for itself.
If you’re not sure what to target, start by picking the one that’s actively annoying you.
How do I separate cold build vs warm/incremental build measurements?
Do it explicitly:
- Cold:
cargo cleanthencargo build - Warm: run
cargo buildagain with no edits - Incremental: edit one file in one crate, rebuild once
Keep the environment constant: same toolchain, same target, same RUSTFLAGS, same feature set.
If you’re in a workspace, always build the same thing. “I ran cargo build” is not a stable benchmark if half the time you’re building examples/tests.
Add build graph visibility: cargo build --timings
Cargo timings is the first diagnostic tool I reach for because it forces the conversation into reality. It generates an HTML Gantt chart that shows crate dependencies and how much parallelism you’re actually getting.
Nicholas Nethercote (Mozilla alum) has the best practical write-up I’ve seen on this whole topic. Read it. Then come back. Nicholas Nethercote.
Run:
cargo build --timings
Open the generated HTML. The “aha” you’re looking for is usually boring:
- One crate eating 30–60% of the total build.
- A long serialized chain where only 1 crate is compiling at a time.
That’s not a “Rust is slow” problem. That’s your crate graph telling you it’s shaped badly.
A snippet-friendly checklist (print this)
- Generate a
cargo build --timingsreport and identify the top 3 crates by time. - Split any crate that serializes the graph (or move heavy code behind a feature).
- Turn on incremental in dev, and keep release separate.
- Install
sccache, verify hit rate, then add remote caching for CI. - Swap the linker to
moldon Linux and measure link time again. - Trim features and optional dependencies until the timing report changes.
- Hunt macro and IR bloat in the top crates (
-Zmacro-stats,cargo llvm-lines).
Visualization (cargo build --timings): find the crates that dominate
How do I measure which crates dominate my Rust build time?
Use the timings report, not vibes.
The timings HTML gives you per-crate durations. Take the top offenders and write them down. If one crate is 9s of a 14s build, you already know where to spend your next hour.
Two patterns show up constantly:
- The “kitchen sink” crate: one crate owns half the domain model, half the macros, half the dependencies. It blocks the whole build.
- The proc-macro chokepoint: everything depends on a proc-macro crate, so compilation waits for it.
Fast builds come from wide graphs. Narrow graphs feel “clean” right up until you have to work in them.
When should I split a workspace/crate to increase parallelism, and what are the tradeoffs?
Split when the timings report shows a crate that:
- takes >25% of build time, and
- sits early in the dependency chain (lots of crates depend on it).
Tradeoffs (the real ones):
- More crates means more
Cargo.tomlsurface area. - You can accidentally increase compile time if you create lots of tiny crates that each pull in the same heavy deps.
- API boundaries harden. That’s good for architecture, but it can feel annoying in early-stage products.
Rule I follow: split by invalidation boundaries. If code changes together, keep it together. If code changes independently, make it a crate.
For example, an “api-types” crate that changes every PR is a terrible foundation crate. Push stable things down (core traits, shared error types), and pull unstable things up.
Cargo profile settings (incremental, lto, codegen-units)
Cargo profiles are where you separate “fast local iteration” from “optimized release binary”. Profiles control compiler settings like incremental, LTO, codegen units, debug info, and assertions. The canonical reference is the Rust Project.
One thing I’ll say out loud because teams keep messing this up: if you optimize for release builds at the expense of dev iteration, you pay for it daily. Don’t do that. Make release expensive on purpose. Make dev cheap on purpose.
What Cargo profile knobs most affect compile time for dev vs release?
The knobs that actually move build time:
incremental: big dev win, sometimes noisy in CI.opt-level: higher is slower.0and1are usually the sweet spot for dev.lto: can add minutes on large binaries.codegen-units: more units can improve parallelism but can change performance and compile time tradeoffs.debug: full debug info can be expensive. Line-tables-only is often enough.
A sane baseline:
dev: prioritize iteration.release: prioritize runtime.
If your team is shipping often, add a middle profile like release-fast for staging builds.
Concrete numbers that matter: on a typical service binary, flipping lto = true can turn a 30s release build into 90–180s, depending on code size. That might be fine for nightly. It’s usually not fine for every PR.
What invalidates incremental compilation most often, and how do I reduce dep-graph churn?
Incremental compilation works when edits stay local. It falls apart when your changes force large parts of the dependency graph to be reconsidered.
The churn culprits that tend to be brutal:
- Editing a foundational crate that dozens depend on.
- Re-export patterns that make “small change” look “global”.
- Heavy macro-generated code that changes in big chunks.
- Build scripts (
build.rs) that embed timestamps or environment-dependent outputs.
Mitigations you can actually ship:
- Keep “core” crates stable. Move fast-changing code upward.
- Avoid needless
pub usefan-out in a root crate. - Make build scripts deterministic. If a script reads the filesystem, lock it down.
- Trim features so fewer crates participate in the build in the first place.
sccache installation and usage (local + CI)
sccache is a compiler wrapper that avoids recompilation when possible and can use local or remote cache backends. That’s straight from the project README: Mozilla.
The mistake people make is treating sccache like a magic on-switch. It’s not. It’s a multiplier. If your build is nondeterministic or your graph invalidates constantly, sccache can’t save you.
Install sccache locally
If you’re already in Rust land, the simplest path is usually:
cargo install sccache
Then set the wrapper:
export RUSTC_WRAPPER=sccache
Verify it’s active:
sccache --versionsccache --show-stats
The stat you care about is “Compile requests” vs “Cache hits”. After a second build, you should see hits.
Configure sccache for Rust in CI (remote cache)
Local caching is nice. Remote caching is what moves your CI bill.
The basics:
- Pick a remote backend your org already trusts (S3, Redis, etc.).
- Ensure the cache key is stable across machines.
The most common reason CI hit rates stay awful is path differences. You check out the repo under /home/runner/work/... in CI and /Users/kunal/... locally, and the compiler bakes paths into artifacts.
sccache supports path normalization via SCCACHE_BASEDIR / base-dir normalization options (see the README section on normalizing paths). If you don’t normalize, expecting >70% hit rate across ephemeral runners is wishful thinking.
How do I verify cache hit rate?
Use:
sccache --show-stats
Track it as a metric.
If your CI does 200 compile requests and gets 20 hits, that’s a 10% hit rate. You don’t have a caching system. You have a warm feeling.
When does sccache not help, and how do I mitigate?
sccache won’t save you when:
- Your code changes invalidate everything.
- You rely heavily on procedural macros that themselves change often.
build.rsscripts emit different outputs per machine.- Your environment leaks into compilation (paths, env vars, feature flags).
Mitigation playbook:
- Normalize paths.
- Freeze toolchains (pin Rust version in CI).
- Make builds deterministic (lock down build scripts).
- Trim features so fewer crates are in the build graph.
If you do all that and still see low hit rates, don’t get religious about caching. Fix the graph and the invalidation boundaries first.
mold installation and usage: cut link time (Linux-first)
mold is designed to be a faster, drop-in replacement for existing Unix linkers. That’s the pitch, and it mostly delivers, per Rui Ueyama.
Linking is often the most annoying part of a Rust build because it’s serial. You can compile crates across 16 cores. Then you hit the link step and watch one core do all the work.
Install mold
On Linux, you typically install mold via your package manager (or build from source).
Quick sanity check:
mold --version
Use mold with Cargo / rustc
There are a few ways to do this; the simplest is setting a linker override via RUSTFLAGS.
For example:
RUSTFLAGS="-C link-arg=-fuse-ld=mold" cargo build
(Exact flags vary by toolchain and platform. The point is: make Cargo use mold as the linker.)
How do I confirm link time improved?
Go back to the timings report.
- Run
cargo build --timingsbefore. - Switch to
mold. - Run
cargo build --timingsagain.
You’re looking for the link step dropping from something like 4–8s to 1–3s on medium-sized binaries. On bigger monorepos, link time can be 10s+. Those are the wins that change how the repo feels.
Platform nuance (macOS + Windows)
- Linux:
moldis the straightforward win. - macOS: you’re constrained by Apple’s linker ecosystem. You can still reduce link work by trimming debug info and avoiding pathological dependency graphs.
- Windows: linker behavior is different again. Don’t assume a Linux linker swap translates.
Measure it. If it didn’t move, undo the change and move on.
Here’s the official demo-style video if you want the quick version before you change a bunch of build plumbing:
Macros (-Zmacro-stats) and LLVM IR bloat: find compile-time blowups
Once you’ve squeezed the “graph and tools” wins, the remaining pain is usually self-inflicted. Macro explosion, generic explosion, or both.
Nicholas Nethercote recommends using the nightly -Zmacro-stats flag to quantify how much code macros generate. See Nicholas Nethercote.
What code patterns cause compile-time blowups, and how do I find them?
Two common offenders:
- Procedural macros generating code on the same order of magnitude as your hand-written code.
- Generics/monomorphization creating lots of instantiations, ballooning LLVM IR.
How to find macro bloat:
RUSTFLAGS="-Zmacro-stats" cargo +nightly build
Numbers to watch:
- If a proc macro generates 200k lines of expanded code, that’s not “just build tooling”. That’s a build-time tax you chose.
- If expanded code is 1x your handwritten code, you should at least question it.
How to find LLVM IR bloat:
cargo llvm-lines
Generic functions are often top offenders because they can be instantiated dozens or hundreds of times in large programs. If a single generic function produces a disproportionate amount of IR, make it smaller or reduce the generic surface area.
This is also where feature trimming pays off. If you can compile 30% fewer crates, you often avoid compiling the most macro-heavy optional stuff entirely.
Feature flag trimming: the most underrated lever
Optional dependencies are not optional to your compile time if you compile with default features everywhere.
A safe workflow I like:
- Run timings with your current features.
- Disable default features on heavy dependencies.
- Add back only what you need, crate-by-crate.
If this sounds like work, it is. But it’s clean work. And it pays you back every single day you touch the repo.
A before/after table (what “good” looks like)
Use a table like this in your repo. Fill it with your own numbers.
| Change | Cold build | Warm build | 1-line edit rebuild | Link time | sccache hit rate |
|---|---|---|---|---|---|
| Baseline | 14.2s | 6.1s | 3.4s | 5.2s | 0% |
| + incremental dev profile | 14.2s | 5.4s | 1.9s | 5.2s | 0% |
| + sccache local | 14.2s | 2.1s | 1.2s | 5.2s | 68% |
| + sccache remote (CI) | 14.2s | 2.1s | 1.2s | 5.2s | 82% |
| + mold | 14.2s | 2.1s | 1.2s | 1.9s | 82% |
Those numbers are illustrative. The important thing is the shape: caching and linker swaps should show up as obvious step changes. If they don’t, your config isn’t working.
One more data anchor, since this blog is obsessed with measurable engineering: based on the LLM pricing tracker I maintain at /llm-prices, small per-unit costs compound brutally at scale. CI minutes are the same. If your org runs 500 builds/day and you shave 60s off each, that’s 8.3 hours of compute time saved every single day.
My opinionated ordering (what to do Monday morning)
If you’re trying to reduce Rust compile time in a real codebase, here’s the order I’d do it:
- Generate timings and identify the top 3 crates.
- Fix the crate graph (split, move code, isolate proc-macros).
- Turn on incremental for dev. Keep release separate.
- Install
sccachelocally. Verify hits. - Add remote
sccachein CI and chase hit rate. - Swap to
moldon Linux. Measure link time. - Hunt macro/IR bloat only in the crates that show up at the top.
Everything else is busywork.
If you want the broader theme behind this, I wrote a metrics-first piece on observability here: [How to Pick LLM Application Observability Metrics [2026]](/blog/llm-observability-metrics). Same energy. Different bottleneck.
And if compile time pain is showing up as workflow friction and review latency, you’ll probably also like my copy-paste process for Stacked PRs on GitHub and the CI framing in AI Code Review in Your CI/CD Pipeline.
My prediction: by the end of 2026, the teams that feel “fast” won’t be the ones with the fanciest codegen or the most magical build system. They’ll be the ones that put build time on the same dashboard as test duration and deploy frequency, and treat the crate graph like architecture, not an accident.
Photo by Daniil Komov on Unsplash.
Kunal Ganglani (2026, August 16). How to Reduce Rust Compile Time [2026] (sccache + mold). Kunal Ganglani. Retrieved August 16, 2026, from https://www.kunalganglani.com/blog/reduce-rust-compile-time


![github pull request review laptop screen — illustration for article on Stacked PRs on GitHub [2026]:](https://img.kunalganglani.com/images/vzekdneq/production/3aec89cec8e78cab8275c7d1c8ceda0785938355-1200x675.webp?auto=format&fit=max&q=75&w=500)
Comments