加速 Rust 编译:用奥卡姆的剪刀把 67000 行代码削减到 422 行
三个月、67,000 行代码变成了422行,在我看来这不是浪费,而是一个验证算法有效性的必经之路。社区的这每一刀,都是在帮我们寻找算法的最小表达。
以下文章来自微信公众号“嘻话 Tech”,作者俞一峻,已获授权。
cargo-slicer 项目手记 · 2026年3月
开场白:一把工具,三个月
我们花了三个月,用 Rust 写了一个叫 cargo-slicer 的工具。它的核心思路并不复杂:通过 BFS(广度优先搜索)找出从程序入口点不可达的函数,然后在 LLVM 编译它们之前,把它们的 MIR(中间表示)主体替换成一条 Unreachable 指令。
结果是可观的:
-
Zed 编辑器:编译时间从 16 分 52 秒降到 11 分 59 秒(-29%)
-
rustc 本身:2 分 16 秒降到 1 分 52 秒(-17%)
工具能用。算法验证了。然后我们把它拿到 Rust 社区,准备提交 MCP(编译器特性提案)。
接下来发生的事,才是这篇文章真正想讲的。
第一刀:lcnr 的手术刀
lcnr 是 Rust 编译器团队的核心成员,负责 trait solver。他在百忙中看了我们的 is_safe_to_skip() 函数——这个函数决定哪些函数可以被"截断"。
他的第一个问题很直接:
"我不理解 '绝对不要截断嵌套函数' 这条规则。”
我们的原始逻辑是:嵌套函数(定义在另一个函数内部的函数)在父函数被内联后,其调用引用会"消失",所以 BFS 看不到它们,不安全。
听起来很有道理。但 lcnr 指出了一个根本性的问题:我们的分析是在 mir_built 阶段进行的,那是内联发生之前。在那个阶段,父函数→嵌套函数的调用边是完全可见的。这条限制是基于一个错误假设建立起来的。
删掉它。
一条规则删除,rustc 基准测试减少了 240 亿条指令(4292B → 4268B,-0.56%)。
他的第二个建议更优雅:
// 我们原来的代码(字符串匹配):
if def_path.contains("::drop") { return false; }
// lcnr 建议的版本(lang item 查询):
if let Some(drop_trait) = tcx.lang_items().drop_trait() {
if tcx.parent(trait_item_id) == drop_trait {
return false;
}
}
字符串匹配不仅脆弱,还会误伤任何路径里带 "drop" 字样的函数。编译器自己有 lang item 注册表,用它。
这就是 Rust 社区的风格:不是"你的方向错了",而是"这里有一把更好的刀"。
第二刀:Amanieu 的另一条路
Amanieu 是 Rust 标准库的leader和Rust编译器的维护者之一。他看完我们的设计后,提出了一个完全不同的思路:
"为什么不用延迟编译?先给库 crate 生成只含元数据的
.rlib占位符,让 Cargo 立刻开始流水线化编译下游 crate,然后在链接前再回头做完整的代码生成。"
这个想法在理论上很美:不需要分析调用图,不需要替换 MIR,只靠 Cargo 的流水线机制。
我们实现了它(CARGO_SLICER_DEFERRED=1),然后测了测。
结果:
|
模式 |
时间 |
vs 基线 |
|---|---|---|
|
基线(无切片) |
2分05秒 |
-- |
|
单次 CGU 过滤(我们的现有方案) |
3分22秒 |
+37% |
|
延迟两阶段编译(Amanieu 的建议) |
3分57秒 |
+90% |
更慢了。
原因清楚了:对于只有两个 crate 的小项目,两次编译的开销远超流水线带来的收益。更要命的是,--emit=metadata 产生的 .rmeta 不包含优化后的 MIR,所以二进制 crate 编译时无法做跨 crate 的单态化——根本行不通。
但这个探索不是失败。它明确了一件事:延迟编译真正有价值的场景,是在 rustc 内部实现,而不是作为 RUSTC_WRAPPER。这个结论后来成为我们提交 MCP 的重要动机之一。
第三刀:workingjubilee 的灵魂拷问
@workingjubilee 是 Rust 编译器贡献者,他的问题切中了最深层的设计缺陷:
"rustc 的驱动层在借用检查完成之后运行
after_analysis钩子。如果你在optimized_mir里返回一个从未经过借用检查的存根 body,编译器的借用检查保证就不再适用于这个函数了。"
这个问题是对的。在 cargo-slicer 的外部实现里,我们确实是在替换 MIR body——用一个单 Unreachable 终结符的存根代替原始 body。存根从未被借用检查器检查过。
我们的答辩是:这些函数是 BFS 证明不可达的,它们永远不会被执行,所以借用检查结论对程序正确性无关。
这在逻辑上成立,但在编译器设计上是个坏味道。
然后 @oli-obk(Oliver Scherer,rustc 编译器团队)补了一刀,方向完全不同:
"其实,把 cargo-slicer 的功能迁移进 rustc 本身,工作量没那么大——核心算法直接用
TyCtxt的原生 API 就能表达。"
这两个评论放在一起,就是答案:不要替换 MIR body,直接从代码生成单元(CGU)里删掉那些函数。collect_and_partition_mono_items 决定哪些函数进入 LLVM 的视野。在那里做过滤,LLVM 永远看不到被删除的函数,借用检查器的问题也不复存在。
奥卡姆的剪刀
我们的外部工具 cargo-slicer 有多少代码?
整个项目:约 67,000 行。
包括:
-
RUSTC_WRAPPER路由和分发逻辑 -
守护进程(fork-server)架构
-
进程间通信(IPC)
-
每个 nightly 版本的 ABI 版本管理
-
磁盘 I/O:
.slicer-cache/文件的读写 -
syn 解析器后端(跨 crate 预分析)
-
ctags 后端
-
延迟编译模式
-
各种兼容性 shim
这包括三个月来尝试过的成功与不那么成功,以及失败的各种中间代码以及几十种算法优化尝试。作为一个软件工程师,这些包含了多少血泪和汗水凝成的宝贵经验啊。
对应的,新的 in-tree 实现 dead_fn_elim.rs:约 422 行。
改动涉及 4 个文件:
|
文件 |
改动 |
行数 |
|---|---|---|
options.rs |
新增一个 |
1 |
dead_fn_elim.rs |
新文件(含跨 crate BFS) |
~410 |
lib.rs
(mir_transform) |
pub mod dead_fn_elim; |
1 |
lib.rs
(driver_impl) |
两个调用点 |
~10 |
这就是奥卡姆剃刀定律在软件工程里的具体形态:如无必要,勿增实体。
外部工具的 6 万多行代码,很大一部分是在对抗 RUSTC_WRAPPER 架构本身的限制——IPC 开销、ABI 版本化、跨进程缓存、磁盘 I/O。当这些限制被消除(移入编译器),核心算法本身只需要 422 行。关键是,如果不直接修改Rust 编译器,Rust编译器驱动的方案需要每天跟社区新出的Nightly编译器代码进行符号对齐,对 CI 造成压力。而用户一旦更新了Rust编译器版本,也必须刷新驱动版本。
倾听的价值
每一次社区反馈,我们都做了实质性的修改,虚怀若谷,从善如流:
-
lcnr:删掉了一条基于错误假设的规则,把字符串匹配换成了 lang item 查询。代码更少,更精确,性能更好。
-
Amanieu:实现了延迟编译,测了,结论是"外部工具做不了",但"in-tree 值得"。这个结论推动了 MCP 的形成。
-
workingjubilee + oli-obk:把 MIR body 替换改成了 CGU 级别删除。从根本上解决了借用检查保证的问题,同时把代码从 6 万行精简到 422 行。
每一次更新带来工程上的收益:奥卡姆的每一刀都让代码更小、更正确、更可维护。
Occam's Scissors,而非 Occam's Razor
奥卡姆剃刀是"不要引入不必要的复杂度",爱因斯坦的KISS原则是“保持足够的简单,如果不是更简单”。
但在工程里,我们更需要的是一把剪刀——主动去剪掉那些已经存在但不必要的复杂度。
这需要两件事同时成立:
-
谦逊:愿意听社区说"这个假设是错的",愿意承认"这条路跑了但跑慢了"。
-
笃定:算法的核心是对的,BFS 从入口点找不可达函数,这件事在 67 KLOC 外部工具里正确,在 422 行 in-tree 实现里也正确。
三个月、67,000 行代码变成了422行,在我看来这不是浪费,而是一个验证算法有效性的必经之路。社区的这每一刀,都是在帮我们寻找算法的最小表达。
422 行,是答案的雏形。当然,用了这个表达以后,编译器性能优化的收益减少到了5~7%,而不是20~30%,但是框架搭好以后,需要极致性能的话,外部crates-slicer工具的更多优化手段还是可以发挥作用的。
从这422行代码的合入过程,你应该理解,Rust编译器的这百万行代码,该是怎样浓缩的存在了吧。
在Agentic AI时代,如果要对大模型生成的代码的安全、性能、可靠性质量兜底,Rust编译器的每一行代码都要经过千锤百炼。能够为它的性能优化贡献这么一点,与有荣焉!
如果你的 Rust 项目编译很慢,可以关注 cargo-slicer 的进展,或者等 -Z dead-fn-elimination 进入 nightly。
项目地址:github.com/yijunyu/cargo-slicer
开放原子旋武开源社区(简称“旋武社区”)是由开放原子开源基金会孵化及运营的技术社区,致力于在中国推广和发展Rust编程语言生态,推动Rust在操作系统、终端设备、安全技术、基础软件等关键领域的产业落地,构建安全、可靠、高效的软件基础设施。
更多推荐


所有评论(0)