How to speed up the Rust compiler in November 2026
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
How to speed up the Rust compiler in November 2026
My first month working on Rust compiler performance for Hexcat is over and it’s time to see some of what’s happened since my last post.
我在 Hexcat 从事 Rust 编译器性能优化的第一个月已经结束,现在是时候回顾一下自上一篇文章以来所取得的一些进展了。
Spans are a critical data structure in the Rust compiler. Every AST, HIR, and MIR node carries a span. Each span has four fields recording the following. A source location (lo and hi). A crate’s source files are concatenated into a single buffer, and these are indices into that buffer. Possibly some hygiene and provenance data (context), relevant for macros and other things. Possibly an extra relative position (parent) that is used to minimize the compile time impact of small changes to source locations, such as those caused by inserting a single line at the start of a file.
Span 是 Rust 编译器中一个关键的数据结构。每个 AST、HIR 和 MIR 节点都携带一个 Span。每个 Span 有四个字段,分别记录以下内容:源代码位置(lo 和 hi)。Crate 的源文件被连接成一个单一的缓冲区,这些字段即为指向该缓冲区的索引。可能包含一些与宏及其他事物相关的卫生(hygiene)和来源数据(context)。可能包含一个额外的相对位置(parent),用于最小化源代码位置微小变化对编译时间的影响,例如在文件开头插入一行代码所引起的变化。
Each of the four fields is 32 bits, resulting in 16 bytes of data. But most of the time these 16 bytes can be compressed into one of three 8-byte inline formats, and the remaining cases are interned into a global table. This works by taking advantage of patterns in the data: many source locations have a small length (i.e. a small difference between lo and hi); many spans have a zero context and/or parent, and the non-zero contexts and parents are typically small integers; parents are only used in incremental builds.
这四个字段每个都是 32 位,总计 16 字节的数据。但在大多数情况下,这 16 字节可以压缩为三种 8 字节内联格式之一,其余情况则被驻留(interned)到全局表中。其原理是利用数据中的模式:许多源代码位置的长度很短(即 lo 和 hi 之间的差值很小);许多 Span 的 context 和/或 parent 为零,而非零的 context 和 parent 通常是较小的整数;parent 仅在增量构建中使用。
#163857: In this PR I replace the old compression scheme with one that is simpler and more efficient. For incremental builds we now typically intern around 1% of spans compared to 20-40% with the old scheme. (In non-incremental builds we typically intern fewer than 0.1%.) Also, we can always store the context field inline even in the interned case, which is useful because we often read the context by itself. This change reduced instruction counts across many benchmarks, in the best cases by more than 3%, and the cycle count improvements were even higher. Incremental builds saw the largest improvements due to less interning.
#163857:在此 PR 中,我用一种更简单、更高效的方案替换了旧的压缩方案。对于增量构建,我们现在通常只需驻留约 1% 的 Span,而旧方案则需要 20-40%。(在非增量构建中,我们通常驻留不到 0.1%。)此外,即使在驻留的情况下,我们也可以始终将 context 字段内联存储,这很有用,因为我们经常单独读取 context。这一更改减少了许多基准测试中的指令计数,在最佳情况下减少了超过 3%,周期计数(cycle count)的改进甚至更高。由于驻留操作减少,增量构建的改进最为显著。
#163971: In this PR Josh Triplett took advantage of the new representation to micro-optimize a number of span operations. This reduced instruction counts across many benchmarks, in the best case by more than 2%.
#163971:在此 PR 中,Josh Triplett 利用新的表示形式对多个 Span 操作进行了微优化。这减少了许多基准测试中的指令计数,在最佳情况下减少了超过 2%。
#164291: In this PR I tried using a pattern type to give Span a niche so that Option would be the same size as Span. It worked but didn’t help performance because we don’t use Option much, and it made the code more complicated. So I abandoned it.
#164291:在此 PR 中,我尝试使用模式类型(pattern type)为 Span 提供一个 niche,以便 Option 的大小与 Span 相同。虽然成功了,但并没有提升性能,因为我们并不怎么使用 Option,而且这让代码变得更复杂了。所以我放弃了这个方案。
#163628: During metadata encoding/decoding and stable hashing we typically need to get information about the same parent span from a hash table over and over. In this PR I put a single-entry cache in front of that hash table, which avoids the hash table lookup in most cases. This reduced instruction counts on many benchmarks, in the best case by more than 4%, mostly benefiting incremental compilation.
#163628:在元数据编码/解码和稳定哈希(stable hashing)过程中,我们通常需要反复从哈希表中获取关于同一个父 Span 的信息。在此 PR 中,我在该哈希表前放置了一个单条目缓存,这在大多数情况下避免了哈希表查找。这减少了许多基准测试中的指令计数,在最佳情况下减少了超过 4%,主要受益的是增量编译。
New trait solver #163981, #164234, #165160: In these three PRs I focused on the compilation time of Diesel, a crate for which the in-development new trait solver is slower than the old trait solver. I did a dozen or so micro-optimizations across the three PRs, such as early returns on hot paths, avoiding allocations, avoiding some recomputations, and optimizing some special cases. These reduced the instruction count overhead of the new solver on a Check Full build from 1.49x to 1.16x, and other new solver benchmarks like typenum and nalgebra also saw improvements. There is still more work to be done but this is good progress.
新的 trait 解析器 #163981, #164234, #165160:在这三个 PR 中,我专注于 Diesel 的编译时间,对于这个 crate 来说,开发中的新 trait 解析器比旧的要慢。我在三个 PR 中进行了十几次微优化,例如在热路径上提前返回、避免内存分配、避免一些重复计算以及优化某些特殊情况。这些优化将新解析器在 Check Full 构建中的指令计数开销从 1.49 倍降低到了 1.16 倍,其他如 typenum 和 nalgebra 等新解析器基准测试也看到了改进。虽然还有更多工作要做,但这已经取得了不错的进展。
Miscellaneous #164236: In this PR I rearranged some code to avoid doing some unnecessary work in an early return case, reducing instruction counts for many benchmarks, in the best case by over 4%.
杂项 #164236:在此 PR 中,我重新整理了一些代码,以避免在提前返回的情况下执行一些不必要的工作,从而减少了许多基准测试的指令计数,在最佳情况下减少了超过 4%。
#165022: In this PR I removed the AttrTokenStream and AttrTokenTree types. These mirrored the widely-used TokenStream and TokenTree types but were only used for cfg processing. This change was mostly about making the code simpler, but it also avoided some token stream conversions, reducing instruction counts on a range of benchmarks: by 4% on the deep-vector stress test, and by less than 1% on some real world cases.
#165022:在此 PR 中,我移除了 AttrTokenStream 和 AttrTokenTree 类型。它们镜像了广泛使用的 TokenStream 和 TokenTree 类型,但仅用于 cfg 处理。此更改主要是为了简化代码,但也避免了一些 Token 流转换,从而减少了一系列基准测试中的指令计数:在 deep-vector 压力测试中减少了 4%,在一些实际案例中减少了不到 1%。
#164965: In this PR I avoided the creation of one type variable per element in literals requiring type inference like vec![1, 2, 3, 4]. This was a huge win (8-12% cycle count reduction) for the deep-vector stress test containing a vec literal with more than 100,000 elements… and didn’t help with any other benchmarks. Sometimes big wins on stress tests translate to smaller wins on real code, and sometimes they don’t. I ended up merging this change anyway because it was very simple and did improve an error message in one test.
#164965:在此 PR 中,我避免了在需要类型推断的字面量(如 vec![1, 2, 3, 4])中为每个元素创建一个类型变量。这对于包含超过 100,000 个元素的 vec 字面量的 deep-vector 压力测试来说是一个巨大的胜利(周期计数减少了 8-12%)……但对其他任何基准测试都没有帮助。有时压力测试中的巨大胜利会转化为实际代码中的较小胜利,有时则不会。我最终还是合并了这个更改,因为它非常简单,并且确实改进了一个测试中的错误消息。
#164341: In this PR I added some additional cases to an early return when processing coercions. This reduced instruction counts across numerous benchmarks, mostly by less than 1%.
#164341:在此 PR 中,我在处理强制类型转换(coercions)时,为提前返回添加了一些额外的情况。这减少了众多基准测试中的指令计数,大多减少了不到 1%。
#165112: There is an existing trivial_consts mechanism in the compiler that skips entire passes (including borrow checking) for trivial consts like const N: usize = 0, to reduce compile times. In this draft PR I have extended this mechanism so it also avoids building MIR for these consts. In the best case (libc) it reduces instruction counts by up to 6%, but trivial_consts is already tricky and somewhat controversial, so the path forward here is unclear. More details are in the Zulip thread.
#165112:编译器中现有的 trivial_consts 机制会跳过诸如 const N: usize = 0 之类的简单常量的整个处理过程(包括借用检查),以减少编译时间。在此草案 PR 中,我扩展了该机制,使其也能避免为这些常量构建 MIR。在最佳情况下(libc),它将指令计数减少了多达 6%,但 trivial_consts 本身就很棘手且存在争议,因此未来的方向尚不明确。更多细节请见 Zulip 线程。
#164484: In this PR Takayuki Maeda introduced a sparse encoding mechanism for metadata. This increased instruction counts slightly, but cycle counts and wall-times were neutral, and more importantly it reduced metadata sizes by up to 37% and max RSS (peak memory) by up to 7%.
#164484:在此 PR 中,Takayuki Maeda 为元数据引入了一种稀疏编码机制。这略微增加了指令计数,但周期计数和挂钟时间保持不变,更重要的是,它将元数据大小减少了多达 37%,并将最大 RSS(峰值内存)减少了多达 7%。
Hexcatconf In October I also travelled to Delft to attend Hexcatconf, a small meetup featuring twenty-ish people from Hexcat, RustNL, and elsewhere. A long trip, but well worth it! It was great to catch up with some familiar faces, make some new friends, and learn more about some of the things people are working on. Thank you to Mara Bos for organizing the event.
Hexcatconf:十月,我还前往代尔夫特参加了 Hexcatconf,这是一个小型聚会,有来自 Hexcat、RustNL 和其他地方的约二十人参加。旅途漫长,但非常值得!很高兴能与一些老朋友叙旧,结识新朋友,并更多地了解人们正在从事的工作。感谢 Mara Bos 组织了这次活动。
Overall progress The wall-time measurements for the period 2026-09-28 to 2026-11-02 that meet the “relevant” threshold can be seen here. It’s a
总体进展:2026 年 9 月 28 日至 2026 年 11 月 2 日期间符合“相关”阈值的挂钟时间测量结果可以在这里看到。这是一个