上游 Rust 维护报告

Lobsters Hottest 新闻

摘要

Sovereign Tech Fellow David Kolozsvari 发布了其 2026 年 8 月至 9 月的 Rust 工具链开源维护报告,涵盖目标目录(target directory)体积缩减、编译时间优化、Polonius 加速、并行前端(parallel frontend)以及多项 Rust 基础设施工作。

<p><a href="https://lobste.rs/s/cdkmaf/upstream_rust_maintenance_report">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/03 02:43

# Upstream Rust 维护报告(2026 年 8–9 月) 来源:https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html 正如我在上一篇报告(https://kobzol.github.io/rust/2026/08/03/stf-june-july-2026.html)中提到的,我目前正以 Sovereign Tech Fellow(https://www.sovereign.tech/news/meet-the-2026-sovereign-tech-fellows)的身份从事开源 Rust 工具链的工作。每两个月,我都会发布一份报告,总结这段时间内完成的开源工作。这是本系列的第二篇。与上次一样,我会挑选几个亮点、总结其余的工作内容,并附上贡献统计数据和已提交 PR 的完整列表。本文详细介绍了我在 2026 年 8 月和 9 月完成的开源 Rust 工作。以下是方便导航的目录: - 减小 target 目录体积(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#reducing-target-directory-size) - 编译时间改进(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#compile-time-improvements) - 尝试优化 Rust 解析器(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#trying-to-optimize-the-rust-parser) - 让 Polonius 更快(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#making-polonius-faster) - 发布并行前端(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#shipping-the-parallel-frontend) - 在 ARM 上测量编译器性能(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#measuring-compiler-performance-on-arm) - 调查增量重编译(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#investigating-incremental-recompilation) - 改进 Rust 编译器合并队列(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#improving-the-rust-compiler-merge-queue) - Josh subtree 迁移(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#josh-subtree-migration) - 跟踪 crates.io 上的 `rust-lang` crates(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#tracking-rust-lang-crates-on-cratesio) - 重新激活无人维护的仓库(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#reviving-unmaintained-repositories) - thanks.rust-lang.org(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#thanksrust-langorg) - rustup-components-history(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#rustup-components-history) - 团队资助活动(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#funding-team-activities) - 其他工作内容(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#other-things-i-worked-on) - 贡献统计与 PR 列表(https://kobzol.github.io/rust/2026/09/30/stf-august-september-2026.html#contribution-statistics-and-pull-requests) ## 减小 target 目录体积 我之前在上一篇报告(https://kobzol.github.io/rust/2026/08/03/stf-june-july-2026.html)以及更早的一篇博客文章(https://kobzol.github.io/rust/rustc/2025/06/02/reduce-cargo-target-dir-size-with-z-no-embed-metadata.html)中都提到过这个倡议:通过移除 Rust crates 的重复元数据来缩小 `target` 目录的体积。在实际项目中,这可以让目录体积减小 5%–35%,效果相当可观。 上次我提到,我们正在等待 Cargo 新的构建目录布局 nightly 实验结束,然后再启用另一个 nightly 默认实验。这发生在 8 月,因此我启用了(https://github.com/rust-lang/cargo/pull/17267)在 nightly 渠道上默认使用 `-Zembed-metadata=no` 的配置,并在 Rust 博客上发布了公告(https://blog.rust-lang.org/inside-rust/2026/08/18/reducing-target-dir-size-on-nightly/)。好消息是,从那以后我们并没有收到任何重大投诉或问题,而且若干构建系统已经成功迁移到了新的机制——元数据只保留在 `.rmeta` 文件中,需要通过 `--extern` 标志显式传递给 `rustc`。 得益于这次实验,我与 Cargo 团队讨论了后续推进方向,并为该功能的编译器部分(即将不稳定的 `-Zembed-metadata` 标志转变为稳定的 `-Cembed-metadata` 标志)提交了一份稳定化报告(https://github.com/rust-lang/rust/pull/163436),目前该报告已进入 FCP 投票(https://github.com/rust-lang/rust/pull/163436#issuecomment-5889363144)🎉。不久之后,Weihang Lo(https://github.com/weihanglo)为 Cargo 侧的功能(默认向 `rustc` 传递 `-Cembed-metadata=no`)提交了一份单独的 Cargo 稳定化报告(https://github.com/rust-lang/cargo/pull/17533)。 还有一些未决问题(https://rust-lang.zulipchat.com/#narrow/channel/246057-t-cargo/topic/Usage.20of.20-Zno-embed-metadata/near/627845724),涉及这对 Cargo 关于 `.rlib`、`.dylib` 和 `.rmeta` 产物的稳定性策略意味着什么,但我认为这不应阻碍编译器侧的稳定化。由于 Cargo 在 nightly 上已经*默认*使用不稳定的 `-Zembed-metadata` 标志,而且 Rust 自身的构建系统也在使用该标志,如果按照通常的做法直接将 `-Zembed-metadata` 改名为 `-Cembed-metadata`,将会立即破坏 nightly 用户的使用体验,除非我们能在 `rustc` 和 Cargo 之间原子化地同步这一变更,而目前这并非易事。 因此,我们的计划是继续在一段时间内同时支持 `-Zembed-metadata` 和 `-Cembed-metadata`,让用户迁移到该标志的稳定版本,然后再最终移除不稳定的标志。这非常令人兴奋,因为看起来我们终于接近可以消除 Rust 磁盘上元数据的重复存储了——这种重复在 Rust 中存在了近 10 年,一直在无谓地膨胀 `target` 目录的体积。 顺便一提,似乎有一个专注于进一步缩小 `target` 目录体积的 Project Goal(https://goals.rust-lang.org/)正在推进中。所以大家还是先别急着去买新硬盘啦! ## 编译时间改进 与上一个周期一样,我仍会花一些时间改进 Rust 编译器的性能,不过这次能拿出的成果并不多。我的实验主要集中在尝试将 arena 分配应用到编译器的各个部分。我必须承认,我并没有取得多少成功,部分原因是我自己对 arena 不够熟悉(不过在实验过程中我学到了很多!),部分原因是编译器代码库的结构,还有部分原因是我现在认为 Rust 的设计在某种程度上对 arena 分配并不友好。不过随着 `Allocator` trait 即将到来的稳定化(https://github.com/rust-lang/rust/pull/156882),情况应该会有所改善。 除了下面描述的几个高层级领域,我还做了一些零散的小型性能优化: - 在 rust#160453(https://github.com/rust-lang/rust/pull/160453)中为编译器中的字符串转义添加了快速路径(感谢 @matthieu-m(https://github.com/matthieu-m)提出的建议(https://github.com/rust-lang/rust/pull/159916#issuecomment-5145000967))。令人惊讶的是,字符串转义可能相当热,而且标准库目前的转义代码(https://github.com/rust-lang/rust/pull/159916)并不是很高效。 - 在 rust#162004(https://github.com/rust-lang/rust/pull/162004)中移除了宏展开系统里一个不必要的 `.clone()` 调用。如果 Clippy 的 redundant_clone(https://rust-lang.github.io/rust-clippy/master/#redundant_clone)lint 能捕获到这种问题就好了,但不幸的是它目前还不够智能。 - 在 rust#162371(https://github.com/rust-lang/rust/pull/162371)中修复了一个此前已合并的 PR 引入的性能回归。 - 在 rust#163412(https://github.com/rust-lang/rust/pull/163412)中为 Cranelift 应用了 LTO。效果并不太理想。我还想试试为 Cranelift 应用 PGO,希望效果会更好一些。 - 尝试在 tracing#3603(https://github.com/tokio-rs/tracing/pull/3603)中减小 `tracing` crate 中宏生成代码的体积,因为我在 bors 日志中看到了大量不必要的生成代码重复。但看起来 `tracing` 已经有一段时间无人维护了,所以我不确定是否会有人处理它。 ### 尝试优化 Rust 解析器 我喜欢阅读其他编程语言和社区是如何实现它们的编译器的。特别是,我一直很欣赏 Zig 编译器施展的那些妙招(https://mitchellh.com/zig/parser#anatomy-of-the-parser)。它采用了数据导向设计/实体组件系统(Entity Component System)的方法来高效实现解析,我觉得这非常酷。我想尝试在 Rust 编译器中对词法分析和解析也使用类似的方法。 需要指出的是,解析 Zig 或 C 与解析 Rust 是截然不同的学科。倒不是说 Rust 的语法本身解析起来有多复杂,而是这个语言的一些设计——解析与宏展开、名称解析交织在一起,并且注重提供*优秀的*诊断信息——使得它的解析逻辑比人们想象的要复杂得多。 目前,Rust 的词法分析器和解析器本质上使用树状表示来表示 token。Token trees(https://github.com/Kobzol/rust/blob/80a4f6cdbdc30afa1f27c830386361d22b161286/compiler/rustc_ast/src/tokenstream.rs#L30)被存储在 `Vec` 中,其中每个树要么是叶子 token(如 `+`),要么是嵌套树的分隔组(如 `(1 + 2)`),后者存储在另一个独立的 `Vec` 中。换句话说,每次解析器在 Rust 文件中遇到圆括号、大括号或方括号时,它都会在堆上分配一个新的 `Vec`。可想而知,这在时间和内存上都不是特别高效。 我尝试将其改为使用扁平表示,即所有 token(包括分隔序列)都存储在单个 `Vec` 中,从而避免所有这些细碎的分配。修改词法分析器非常简单,在本地测试中词法分析性能提升了约 30%,效果不错。然而,将这一改动扩展到解析器、宏展开和 AST 处理就相当……复杂了¹。一次性在所有地方更改表示形式并不现实,因为有*大量*代码访问 `TokenStream`(https://github.com/Kobzol/rust/blob/80a4f6cdbdc30afa1f27c830386361d22b161286/compiler/rustc_ast/src/tokenstream.rs#L639)类型,该类型抽象了 token 树的 `Vec`。 因此我不得不在旁边另建一份实现(使用扁平的 token `Vec`),用扁平 token 结构重新实现所有旧功能,并实现双向转换函数(从扁平转换为嵌套树,以及从嵌套转换为扁平树)。之后我需要在编译器中逐步迁移 `TokenStream` 的用法到新的表示形式,将转换逻辑不断上移到更高层级的调用点,直到所有的转换都被消除。 经过大量工作,我在 rust#159378(https://github.com/rust-lang/rust/pull/159378)中取得了相当大的进展。不过目前的性能结果并不十分理想(https://perf.rust-lang.org/compare.html?start=a8a1e6fd9df2e094d6f09c0d57991508680acc1c&end=270a6702b0cd5877ec2a39d3fa823beb31b1e9a0&stat=instructions:u):使用扁平 token 表示后,编译器实际上变得更慢了。我取得的最好成绩是这一次(https://perf.rust-lang.org/compare.html?start=fd7ed57dfd3bdebb745a1d8158638727b0e7047a&end=41bfd27447b6da2214d1c73b14037a50d34b84c3&stat=instructions:u),但当我开始把更多内容迁移到新表示形式后,性能反而回归得更严重了。 原因主要在于宏展开。当需要对已解析的 token 列表进行大量修改时,每个分隔组作为独立分配的嵌套树表示其实相当有用——而这正是声明式宏和过程宏展开时会发生的事情,也发生在解析器的其他几个地方(通常在属性周围),我们目前会对输入的 token 流做一堆就地修改(这有点吓人,也算是一种 hack)。 我认为,要么可以修改那些代码使其更适应扁平表示,要么可以采用某种混合表示形式,将扁平表示的性能优势与嵌套表示易于修改的特点结合起来。不过我不太确定具体会是什么样子。 还可以通过以下方式获得进一步的性能收益: - 减小单个 token 的体积。目前每个 token 大约 40 字节,这有点荒谬。 - 使用分块 `Vec`,这样在扁平列表末尾追加时,当 `Vec` 内存耗尽就不必重新分配并复制整个 token 列表。 - 将 `AttrTokenStream`(https://github.com/Kobzol/rust/blob/80a4f6cdbdc30afa1f27c830386361d22b161286/compiler/rustc_ast/src/tokenstream.rs#L349)也迁移到扁平表示形式,这样在处理属性时就不必在扁平表示和单独分配的 token 树之间来回转换了²。 我认为这可能是一个性能结果一直保持红色、直到最后一个瓶颈/转换点被消除为止的情况,届时结果有可能大幅转为绿色。但到目前为止,达到那个点似乎相当困难 :) 在紧盯着 token 解析代码许多周之后,我还注意到存在其他低效的来源。例如,解析器在解析某些构造之前会快照其状态,以便稍后可以惰性地重新解析代码的某些部分。这种快照操作相当频繁(在 bors 上我测得超过了 25 万次快照),而且开销不小。这听起来可能有些出人意料;解析器状态难道不就是指向输入字符串中某个位置的一个数字吗?唉,但愿如此 :) 问题在于,由于分隔组的表示方式,我们实际上需要记住一个当前活跃的父级分隔组栈,这样当遇到某个分隔组的结束时,我们才知道该回溯到解析器的哪个位置。 举个例子,当我们正在解析 `(1 + array[0] + 2)`,而解析器位于 `[0]` 这个分隔组结束的位置时,它必须能够返回其父级分隔组(`(1 + ...)`),以便访问其数据。让我感到有些恼火的是,我怀疑大多数时候我们最终根本用不上其中大部分的父级信息,但我们仍然要为克隆整个父级栈付出代价。这有点蠢,所以我尝试在 rust#162593(https://github.com/rust-lang/rust/pull/162593)中找出一些办法,避免每次做快照时都克隆包含多个元素的 `Vec`。我尝试了: - 在分隔组栈的条目中存储侵入式指针,这样快照操作至多只会克隆一个父级条目,我可以顺着指针找到它的父级。但这用快照性能换来了在遇到每个分隔组时的额外一次分配(以便我们在某处存储父级信息),结果导致了性能回归。 - 使用不可变数据结构(如 `im`(https://docs.rs/im/latest/im/) 等 crate)来降低克隆的开销。这同样导致了性能回归。 - 我考虑过在分隔组内部使用 `Arc` 来记住它们的父级,但我很快放弃了这个想法,因为要让这套机制运作起来需要大量工作,而且很可能会遇到……

相似文章

如何在2026年9月加速Rust编译器

Lobsters Hottest

该文章报告了Rust编译器在两个月内平均编译时间减少了4.57%,重点介绍了rustdoc、Clippy的优化、LLVM升级,以及新的Polonius借用检查器和Penelope Hammertime特征求解器。

如何在2026年7月加速Rust编译器

Lobsters Hottest

Nicholas Nethercote报道了Rust编译器近期性能改进,包括平均墙钟时间总体减少5.59%,rustdoc大幅加速总计28%,以及通过PR和PGO训练更改实现的显著Clippy优化。

认识新的 Sovereign Tech Fellows

Lobsters Hottest

Sovereign Tech Fellowship 在 2026 年扩大规模,支持 14 名开源维护者、社区经理和技术写作者,覆盖 Rust、Python 及其他生态系统,重点关注安全性、可持续性和社区。

我是如何在一周内让Rustdoc快33%的

Lobsters Hottest

一位Rustdoc团队成员通过一系列优化和错误修复,在Rustdoc中实现了33%的性能提升,解决了影响文档生成的递归限制问题。