为什么 Rust 需要特殊的基准测试方法?
Rust 以其速度著称,但即使是最快的语言,糟糕的实现也会拖慢它。您是否曾经编写过一段 Rust 算法,心想:“这肯定会快如闪电!”,然后运行起来却不明白——微秒都浪费在哪里了?熟悉吗?
Rust 中的基准测试不仅仅是启动计时器和测量时间。这是一门科学:需要考虑编译器优化、CPU 缓存,甚至 Rust 管理内存的方式。在本文中,我们将探讨如何正确地对 Rust 程序进行基准测试,以便您获得真实的数据,而不是随机的结果。
我们将从使用 std::time 的简单测量开始,一直到专业工具 criterion(实际项目中使用的工具)。开始吧!
1. 最简单的方法:std::time::Instant
如果您想快速“感受”一段代码的性能——无需安装任何 crate,只需将测量代码直接插入 main 函数即可。但有一个陷阱:如果您的代码没有使用其结果,Rust 优化器可能会“丢弃”它。
use std::time::Instant;
fn main() { let start = Instant::now();
// 您需要测量的代码 let mut sum = 0u64; for i in 0..1_000_000 { sum += i; }
let duration = start.elapsed(); println!("执行时间: {:?}", duration); // 务必使用结果! println!("总和: {}", sum);}为什么不能只测量一次运行?
单次运行就像抽奖。系统可能正忙,CPU 可能降频,缓存可能是冷的。Rust 中正确的基准测试应该:
- 预热代码 (warm-up) —— 在测量前进行几次迭代
- 多次运行代码(数千次迭代)
- 对结果取平均值
手动实现这些很容易出错。因此,Rust 社区发明了 criterion。
2. 专业基准测试:Criterion crate
Criterion.rs 是 Rust 中基准测试的事实标准。它被用于编译器 rustc 本身以及数百个 crate 中。它为什么这么棒?
- 自动预热和统计处理
- 图表以及与之前运行结果的比较
- 检测性能回归(如果代码变慢,您会知道)
- 支持参数化基准测试
安装与配置
在 Cargo.toml 中添加:
[dev-dependencies]criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]name = "my_benchmark"harness = false创建文件 benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
// 被基准测试的函数fn fibonacci(n: u64) -> u64 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), }}
fn bench_fibonacci(c: &mut Criterion) { c.bench_function("fibonacci 20", |b| { // black_box — 阻止优化器丢弃代码 b.iter(|| fibonacci(black_box(20))) });}
criterion_group!(benches, bench_fibonacci);criterion_main!(benches);运行:cargo bench。您将看到类似这样的输出:
fibonacci 20 time: [24.567 µs 24.789 µs 25.012 µs]三个数字是置信区间(下限、平均值、上限)。如果区间很窄,说明基准测试稳定。
3. 秘密武器:black_box
您可能注意到了示例中的 black_box。这是 criterion 中的一个神奇函数,它阻止 LLVM 优化参数或结果。没有它,您的基准测试可能显示 0 纳秒——因为编译器在编译阶段就直接计算出了常量。
示例 p