3133X优化单个Rust Clippy lint
摘要
文章解释了Rust中的`clippy::nonstandard_macro_braces` lint如何被3133X优化,重点在于通过解决编译过程中宏展开的低效问题来提高性能。
<p><a href="https://lobste.rs/s/jtmyg2/optimizing_single_rust_clippy_lint_by">评论</a></p>
查看缓存全文
缓存时间: 2026/09/12 22:42
# 将单个Rust Clippy检查规则性能提升3133倍
原文:https://blog.goose.love/posts/making-a-clippy-lint-faster-by-3133x/
本页面使用**0个Cookie!**(我不确定是否有人阅读,如果有,请通过Mastodon告诉我:https://tech.lgbt/@blyxyas)
> 若希望直接查看代码,可访问此链接:https://github.com/rust-lang/rust-clippy/pull/16808
`clippy::nonstandard_macro_braces`是Clippy的一项检查规则,用于捕获使用错误花括号的宏调用。
在Rust中,若要实例化空向量,可调用宏`vec![]`。但技术上并不妨碍你使用`vec!()`,甚至*更离谱*的`vec! {"???"}`。
然而,所有人都讨厌这种写法。没有人喜欢`println! {}`。
因此,Clippy提供了完美的检查规则来避免你成为社区中的新手。正如前文提到的`clippy::nonstandard_macro_braces`(https://rust-lang.github.io/rust-clippy/stable/index.html?search=nonstandard_macro_braces),其中硬编码了常见宏及其惯用花括号格式。
注意,Clippy在**宏展开后**运行。这意味着,例如对于`println! {"..."}`,Clippy实际看到的是:
```rust
std::io::_print(std::format_args_nl!("Hello, world!"));
```
请花一分钟仔细观察。
根据这段代码,花括号信息究竟存储在何处?我相信你很聪明,正因如此你**错了**
我们无法在展开后的检查中得知原始使用了何种花括号。Rust没有明确定义的宏调用映射表,这可能是Clippy面临的最大痛点。
因此我们首先需要确认是否处于宏展开环境中——正如你所料,通过检查是否在宏展开阶段。Rust有时能识别当前代码是否来自宏展开。
现在只剩下一步:沿着展开链逐级回溯:
- 若当前处于宏环境:
- 向上追溯1层,检查"外部展开数据",即产生当前展开的宏信息
- 若生成者是宏,则获取其名称及对应的惯用花括号格式
接着我们运用源码文本技巧,根据卫生数据中的宏展开信息获取对应代码段。
于是我们得到:
```rust
std::io::_print(std::format_args_nl!("Hello, world!"));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|- 通过静态"会话全局变量"查询卫生数据,获取代码范围"src/main.rs:1:16 至 src/main:1:32"
```
通过存储卫生数据的会话全局变量,我们可以定位到"文件src/main.rs,第1行,第16列"至"文件src/main.rs,第1行,第32列"的代码段。该行对应以下字符串:
通过字符串处理(按`!`分割,修剪第二部分并检查首字符是否为`[`、`(`或`{`),我们终于识别出花括号类型!
这意味着我们:调用了两次卫生数据函数,锁定了符号互斥锁(将代码标识符转换为字符串形式以便比较的系统),并进入了递归循环。
**针对每个表达式**。
你的怀疑**完全正确**,我们确实在为整个代码库中的每个表达式、语句**和**项目调用所有这些代码。
这意味着我们为代码中**几乎每个逻辑单元**调用会话全局变量(实际阻塞编译器其他所有操作)**同时**锁定符号互斥锁(拖慢编译器其他所有操作)。
需要明确说明:这并非某个贡献者的错误操作或恶意行为。**责备贡献者绝非正确做法**。
**责任在我们维护者身上**。我们的职责是让写出糟糕代码变得尽可能困难。
Rust已为我们处理了困难部分:无需考虑缓冲区溢出或释放后使用错误(除非进行特殊操作,否则这些问题不会频繁出现)。
是的,这有时意味着编写更多代码。是的,这意味着不能过度使用宏。更意味着不能抽象到失控的程度——否则某个看似无害的函数(作为贡献者你可能已见过上百次)可能占据Clippy 25%的运行时间。
作为开源维护者,我们有三项职责:
1. 抱怨功能需求(这是最重要的)
2. 不干扰用户工作流(神圣不可侵犯)
3. 减少缺陷
显然,如果代码出现问题,说明我们未能尽职。
修复方案?代码量不足200行。我只需将问题函数从展开后检查改写为展开前检查。
不再需要获取源码文本并通过*数学计算*确定括号范围。不再锁定符号互斥锁,不再锁定会话全局变量。
唉,我知道——如果你了解Clippy就会知道:检查规则通常在展开后运行。展开前代码不可信,它会误导你。但这确实是个临时方案,不过它为计算资源节省了数十万美元。
## 最小化未来性能问题
Clippy现在拥有性能基准测试服务器。没错,我近4年来一直尝试构建这样一个系统,如今终于完成。感谢Rust基金会的合同支持,我能够极大扩展工作规模。现在我们拥有可存储200天数据的基准测试服务器。
系统完全自主托管(至少部分如此),并且采用本地基准测试,因此测试数据应非常接近真实用户体验(我使用的CPU架构十分常见)。
如想了解部署详情,请发送邮件。
---
感谢阅读,下次Clippy实现突破性改进时再见(或者可能是其他方面的进步...)
相似文章
如何在2026年7月加速Rust编译器
Nicholas Nethercote报道了Rust编译器近期性能改进,包括平均墙钟时间总体减少5.59%,rustdoc大幅加速总计28%,以及通过PR和PGO训练更改实现的显著Clippy优化。
我是如何在一周内让Rustdoc快33%的
一位Rustdoc团队成员通过一系列优化和错误修复,在Rustdoc中实现了33%的性能提升,解决了影响文档生成的递归限制问题。
优化 #[sqlx::test] 的重建时间
一位 Rust 开发者对 SQLx 测试的增量重建时间进行了性能分析和优化,识别了调试信息生成和过程宏开销等瓶颈,并提出了加速测试编译的改进方案。
用 Rust 重写
本文评估了2026年的‘Rewrite It In Rust’运动,讨论了现实世界中的性能提升、诸如新错误和平台支持等挑战,并提倡增量重写而非完全重写。
@charliermarsh:我一直在为 cargo-fixit 贡献代码:这是一个更快、可直接替代 `clippy --fix` 的工具。这里有一个极端的例子……
Charlie Marsh 宣布了 cargo-fixit,一个更快的、可直接替代 clippy --fix 的工具,在 Codex 仓库上实现了 120 倍的加速。