blog.rust.gay

How to speed up the Rust compiler in November 2026

Rust compile times are a practical constraint for anyone working on medium or large codebases. Faster builds shorten feedback loops, reduce CI cost, and make incremental development less disruptive. The Rust project treats compiler performance as ongoing engineering work rather than a one-time fix: changes are measured continuously, regressions are investigated, and improvements come from many small, targeted adjustments across the compiler.

This post explains how Rust compiler speedup work is tracked, how to interpret performance reports, and which parts of the compiler are common targets for optimization. As a concrete case study, it summarizes Nicholas Nethercote's November 2026 report on recent rustc improvements. The specific pull requests, percentages, and measurement window discussed in that case study come from Nethercote's analysis and should be read as reported results from that period, not as guarantees that every project will see the same gains.

Where Rust compiler performance data comes from

The main public dashboard for Rust compiler performance is perf.rust-lang.org. It tracks benchmark results over time and provides views such as graphs, comparisons, dashboards, toolchain information, and status pages. The data is produced by the rustc-perf infrastructure, which runs benchmark compilations for selected compiler revisions and records metrics.

Performance tracking matters because compiler changes interact in non-obvious ways. A refactor that simplifies code can add allocations on a hot path. A new language feature can add work to parsing, type checking, borrow checking, or code generation. Conversely, a small data-structure change can improve many benchmarks at once. Continuous measurement makes those trade-offs visible.

For readers, the important point is that a speedup report is usually a summary over a benchmark suite, not a universal claim. Results depend on the benchmark set, the build mode, the machine and configuration used for measurement, and whether the comparison includes only changes above a noise threshold or also smaller differences.

How to interpret speedup reports

Compiler performance numbers are easy to misread if the metric is unclear. Different metrics answer different questions:

Wall time measures elapsed time for a compile. It is closest to what developers feel, but it can be noisy because it is affected by system load, I/O, scheduling, and other processes. Wall-time comparisons are useful when they are averaged over enough runs and interpreted with variance in mind.

Instruction count measures how many CPU instructions were executed. It is often more stable than wall time for compiler benchmarking because it is less sensitive to short-term system noise. A lower instruction count usually suggests less work, but it does not always translate directly into the same wall-time improvement, especially when memory behavior or parallelism changes.

Maximum RSS (resident set size) measures peak memory usage during the build. Memory matters for developer machines, CI runners, and large projects where parallel jobs compete for RAM. A change that is neutral on time but reduces peak memory can still be valuable.

Build modes also change the meaning of a result. A "check" build exercises parsing, macro expansion, name resolution, type checking, and borrow checking more than code generation. A full debug or release build includes more backend work. Incremental builds reuse previous compiler state and can behave very differently from clean builds. When a report says a change helps incremental builds, that is especially relevant to day-to-day editing workflows.

Benchmark scope matters. Some optimizations help stress tests with unusual shapes, such as extremely large literals or deeply nested types, but have little effect on ordinary crates. Other changes help common paths in metadata handling or trait solving and show up across many real-world benchmarks. A careful report distinguishes broad improvements from narrow wins.

When reading any rustc performance summary, look for the measurement window, the number of benchmarks or tests, the metric used, whether results are filtered for relevance, and which build profiles were affected. If a report includes per-PR results, check whether the same PR improved time, memory, or both, and whether the effect is concentrated in one workload or spread across the suite.

Common areas where compiler work pays off

Rust compiler performance is not controlled by a single bottleneck. The compiler moves data through several stages, and each stage has its own costs. Common optimization areas include:

Front-end data structures and metadata

Every stage of compilation needs source locations and context. Spans, identifiers, symbols, and metadata are touched constantly, so small inefficiencies can multiply. Compact representations, careful interning, and avoiding unnecessary hash-table lookups can reduce instruction counts across many benchmarks. This kind of work is not flashy, but it often produces broad improvements because it affects hot paths used by many compiler queries.

Metadata encoding and decoding are especially important for incremental compilation and for crate boundaries. Smaller metadata can reduce memory pressure, and faster access to metadata can reduce time spent reading dependency information. When a report highlights metadata size or max RSS reductions, it is pointing at costs that affect scalability as projects grow.

Type checking and trait solving

Trait resolution is central to Rust's type system and can be expensive in crates with complex generic code. Improvements here often come from algorithmic changes combined with careful micro-optimization: avoiding repeated work, returning early on common cases, reducing allocations, and specializing paths that appear frequently in practice. Because trait solving interacts with many language features, performance work in this area has to preserve correctness and diagnostics while reducing overhead.

Reports about a new trait solver or changes to type inference are worth reading cautiously. A prototype may be correct but slower on certain workloads; later passes may recover performance through targeted fixes. The useful question is not only whether a component is new, but how its cost compares on representative crates and whether the remaining regressions are understood.

Middle-end analysis and code generation boundaries

After type checking, the compiler performs analyses such as borrow checking and generates MIR before moving toward LLVM or another backend. Some compile-time work can be avoided for trivial cases, but shortcuts in this area require care because they can affect diagnostics, internal invariants, or future compiler changes. When a report mentions skipping MIR generation or bypassing a pass for trivial constants, that is an example of trading generality for speed in a narrowly defined case.

Backend experiments also appear in performance discussions. A backend change can affect compile time, runtime performance of generated code, or both. Those are different goals. A faster backend compile path does not automatically mean faster binaries, and a backend focused on fast debug builds may make different choices than one focused on optimized release output.

Parallelism and scheduling

The compiler can do some work in parallel, but parallel speedups depend on data access patterns, synchronization, and whether tasks are coarse enough to amortize overhead. Parallelism is not a substitute for efficient single-threaded work; it often amplifies the need for compact data structures and low-latency access. If threads spend time waiting on shared state, adding more concurrency may not help.

When evaluating parallel frontend work, look for evidence that the change reduces contention or makes shared data cheaper to access. Improvements to spans, caches, and metadata can be prerequisites for better parallel behavior because they reduce the cost of operations that many threads perform.

Case study: the November 2026 report

Nicholas Nethercote's post, "How to speed up the Rust compiler in November 2026," reports work from his first month focused on compiler performance for Hexcat. The report combines targeted pull requests with benchmark measurements over a defined period. The details below summarize what that report describes; they are useful as examples of how compiler performance work is communicated and measured.

Span representation and metadata access

Nethercote identifies spans as a critical data structure because AST, HIR, and MIR nodes carry span information. In the report, a span contains source location endpoints, optional hygiene or provenance context, and an optional parent used to reduce the compile-time impact of small source edits, especially in incremental builds. The report states that each of those four fields was 32 bits, for 16 bytes total, and that most spans could be compressed or interned based on common patterns such as short lengths, sparse context, and conditional parent usage.

According to the report, PR #163857 replaced the previous compression scheme with a simpler and more efficient representation. Nethercote reports that for incremental builds the compiler typically interned around 1% of spans under the new scheme, compared with 20–40% before, and fewer than 0.1% in non-incremental builds. The report also notes that the context field could be stored inline even in the interned case. The reported effect was instruction-count reductions across many benchmarks, with the best cases over 3%, and larger cycle-count improvements in some cases. Incremental builds saw the largest gains because interning was reduced.

The report then describes follow-up work. PR #163971, by Josh Triplett, used the new representation to micro-optimize span operations, with reported instruction-count reductions above 2% in the best case. PR #164291 attempted to give Span a niche so Option<Span> would be the same size, but Nethercote reported that it did not help performance because Option<Span> is not used much; the attempt was abandoned. PR #163628 added a single-entry cache in front of a hash table used during metadata encoding/decoding and stable hashing to reuse information about the same parent span, with reported instruction-count savings above 4% in the best case, mostly benefiting incremental compilation.

These changes illustrate a common pattern: make a core data structure smaller or cheaper to access, then exploit the new representation with focused micro-optimizations and caches.

Trait solver work

The report also covers work on the in-development new trait solver. Nethercote describes Diesel as a crate where the new solver was slower than the old one and reports targeted micro-optimizations in PRs #163981, #164234, and #165160. The examples given include early returns on hot paths, avoiding allocations, avoiding some recomputation, and optimizing special cases.

For a Check Full build of Diesel, the report states that the instruction-count overhead of the new solver dropped from 1.49x to 1.16x. Nethercote also reports improvements on other solver-related benchmarks such as typenum and nalgebra, while noting that more work remained. This is a useful reminder that compiler performance engineering is often iterative: an initial implementation establishes correctness and coverage, then measurement guides the next round of reductions.

Targeted fixes and experimental ideas

Nethercote's report lists several smaller changes with different risk and payoff profiles. PR #164236 avoided unnecessary work in an early-return path and was reported to reduce instruction counts by over 4% in the best case. PR #165022 removed AttrTokenStream and AttrTokenTree, types used for cfg processing, simplifying code and avoiding token-stream conversions; the report cites a 4% improvement on the deep-vector stress test and less than 1% on some real-world cases.

PR #164965 avoided creating one type variable per element in literals requiring type inference, such as vec![1, 2, 3, 4]. Nethercote reports an 8–12% cycle-count reduction for the deep-vector stress test with a very large vec literal and no help for other benchmarks, but merged the simple change because it improved an error message in one test. PR #164341 added early-return cases when processing coercions, with reported improvements mostly below 1% across numerous benchmarks.

PR #165112 is described as a draft that extends the existing trivial_consts mechanism to avoid building MIR for trivial constants such as const N: usize = 0. The report states up to 6% instruction-count reduction in the best case (libc) but notes that the mechanism is already tricky and controversial, with the path forward unclear. PR #164484, by Takayuki Maeda, introduced sparse encoding for metadata; Nethercote reports a slight increase in instruction counts, neutral cycle counts and wall times, up to 37% smaller metadata, and up to 7% lower max RSS.

Taken together, these examples show why compiler performance summaries often mix clear wins, narrow wins, and trade-offs. Memory, instructions, and wall time do not always move in the same direction.

Aggregate results reported for the period

For the period from 2026-09-28 to 2026-11-02, Nethercote reports wall-time results above a relevance threshold with improvements on 236 tests across 45 benchmarks and regressions on four tests across three benchmarks. When including results below variance thresholds, he reports 599 improvements versus 90 regressions. The reported means over that broader selection were a 2.85% wall-time reduction and a 4.46% max-RSS reduction.

The report also highlights PR #164484's metadata-size reduction of up to 37% and max-RSS reduction of up to 7%. Looking at instruction-count changes since the start of 2026, Nethercote reports 560 improvements and one regression out of 561 measurements, with the second-worst improvement at 3.10%, crediting Matyáš Racek for the observation. These numbers are specific to the cited measurement window and benchmark selection; they are best understood as evidence of sustained improvement in that dataset, not as a fixed percentage every user should expect.

What developers can take from compiler performance work

Most Rust users will not change rustc internals, but they can still use performance reports well. First, match the metric to the problem: if CI is slow, wall time and cache behavior matter; if machines run out of memory, max RSS matters; if a change is hard to measure locally, instruction-count trends from rustc-perf can provide a stable signal. Second, compare like with like: clean versus incremental, check versus full builds, debug versus release, and the same dependency graph.

Third, treat project-specific behavior as normal. A crate dominated by macro expansion, generated code, trait-heavy generics, or large dependency metadata may respond differently from the benchmark suite average. When a compiler release or nightly change feels faster or slower, it can help to reproduce the observation with a consistent command, capture the profile or timing data available, and then check whether the behavior aligns with broader reports.

Finally, remember that compiler speed is one part of Rust's cost model. Faster compilation helps, but it does not remove the need to understand build modes, dependency choices, feature flags, and workspace structure. The value of public performance work is that it makes the compiler's behavior more observable: developers can see what changed, how it was measured, and which trade-offs were accepted.

Conclusion

Rust compiler performance improves through a mix of measurement, data-structure design, algorithmic work, and careful validation. Dashboards such as perf.rust-lang.org and reports from compiler contributors provide the evidence needed to tell broad wins from narrow ones and time savings from memory savings. Nethercote's November 2026 report is a useful example: it ties specific pull requests to measured outcomes, explains where the gains came from, and distinguishes strong results from experimental or limited changes.

Reading those reports with attention to metrics, build modes, and benchmark scope makes them more useful than a single headline percentage. The long-term goal is not a one-off speed record but a compiler that keeps getting cheaper to run as Rust codebases and language features continue to evolve.

Sources


← back to posts