mcpField
R
rust-performance

rust-performance

github.com/rewrite-rs其他1040 次安装10 周前更新
代码仓库

关于 rust-performance

面向 Rust 编码 Agent 的性能分析与优化技能,强调先测量再修改。内容涵盖基准比较、分配与集合选择,以及编译器设置的权衡。

主要功能

  1. 01优化前先进行性能分析
  2. 02介绍 Criterion 和 divan 基准测试
  3. 03讲解内存分配复用模式
  4. 04按访问模式选择集合
  5. 05说明哈希和代码生成的权衡

适用场景

  1. 01定位 Rust 性能瓶颈
  2. 02依据基准基线比较改动
  3. 03减少热点路径中的内存分配

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rewrite-rs/skills --skill rust-performance

安装后在 Claude Code 中输入/rust-performance确认已加载。

附带文件agents/openai.yamlALLOCATION.md

详细介绍

Rust 性能

这里的每项改动都必须有明确的测量依据:性能分析是入场券, 代码生成标志只贡献最后 5%,而不是第一步。异步吞吐量见 /async-rust;用 unsafe 提速见 /unsafe-rust。

先测量,再改动

任何优化都必须有明确指出它的测量依据。跳过这一步要付出双重代价: 一是改动没有带来任何提速,二是改动让代码变差,却什么也没换来。性能分析会找出 真正耗时的那个函数——几乎从来不是读代码时看起来开销最大的那个。

有意义的基准测试

用 criterion 与已存储的基线进行统计比较;若轻量级测试框架就够用,则用 divan。对操作本身进行基准测试,而不是测试其外围的准备工作。

没有它,性能分析器显示的将是地址,而不是函数名:

[profile.bench]
debug = 1

在热调节限频的笔记本电脑上运行基准测试,得到的数字虽然有意义,但无法比较—— 进行对比的测试运行之间,机器必须保持空闲。

答案通常是减少分配

这是收益最高的类别,也是性能分析最常指出的问题:

  • 已知大小时使用 Vec::with_capacity——默认行为会扩容并复制。
  • 清空并复用,而不是每次迭代都分配:在外部创建缓冲区,在内部调用 clear()。
  • 对永远不会扩容的拥有型序列,使用 Box<[T]> 和 Box<str>。
  • 使用 mem::take 从 &mut 中移出值,而不进行克隆。
  • 热循环中不要使用 format!——每次调用都会分配一个新的 String。
  • 对可借用或拥有的情况使用 Cow——仅在值发生变化时复制。

一个完整示例:使用已知的总大小进行拼接:

fn joined(parts: &[String]) -> String {
    let total = parts.iter().map(|p| p.len() + 1).sum();
    let mut out = String::with_capacity(total);
    for (i, part) in parts.iter().enumerate() {
        if i > 0 {
            out.push(' ');
        }
        out.push_str(part);
    }
    out
}

复用模式和传递缓冲区的写法见 ALLOCATION.md。

根据访问模式选择集合

对于较小的 n,Vec 比 HashMap 更快,原因是缓存行为,而不是渐近复杂度。 需要迭代顺序时使用 BTreeMap。若只构建一次、读取多次,则使用排序后的 Vec 加 binary_search。临界点需要测量,而不是背下一个固定值。

哈希

默认哈希器可抵御 DoS,因此速度相应较慢。只有键不受攻击者控制时,替换它才安全且 值得——例如内部 ID、枚举判别值、驻留字符串。若键由攻击者控制,这就会成为 拒绝服务攻击面,再快的查找也弥补不了这一点。应明确说明风险,而不是把替换说成 毫无代价的收益。

迭代器与边界检查

迭代器链会向优化器提供索引循环所隐藏的长度不变量,这正是边界检查能够消失的原因: 同一循环中的两次 [i] 读取可以改为 iter().zip(),优化器便能证明访问不会越界。 这里是惯用写法也能带来速度的一个例子——没有任何取舍需要争论。

最后 5%

LTO、codegen-units = 1、PGO、target-cpu。根据 ADR 0007:这些是应用层决策, 绝不能设为库的默认值;每项都应同时说明可移植性代价与收益——调整参数只需一行, 性能分析却要花一个下午:

  • 在提交的配置中使用 target-cpu=native,会生成无法在较旧机器上运行的二进制文件。
  • PGO 需要有代表性的工作负载,否则优化的将是错误的执行路径。
  • LTO 和 codegen-units = 1 带来的只是百分比级提升,而非倍数级提升,代价是更长的构建时间。

panic = "abort" 不是性能默认设置:这是应用层的选择,库不能假定会进行栈展开, 而且它会改变 #[should_panic] 下的行为。

延后处理

异步吞吐量、执行器停顿和 spawn_blocking 属于 /async-rust——这是运行时问题, 而不是吞吐量问题。若要使用 unsafe 跳过检查,请看 /unsafe-rust;那里的答案是 该代码块需要有健全性论证,而不只是基准测试。性能分析找出的克隆操作是否根本不该 存在,见 /ownership-not-clone。

验证

cargo bench                  # against the recorded baseline where one exists
cargo clippy --all-targets   # at the level the repo configures; add -- -D warnings only if it configures none
cargo test --release

没有存储基准的基准测试无法证明任何变更有效——先记录变更前的数据,再测量变更后的数据,并与基准测试报告的波动范围进行比较。Clippy 会保留目标仓库配置的 lint 级别;对于完全未配置 lint 的仓库,-D warnings 是备用方案。

评论

支持 Markdown