Rust 语言中文版 基准测试

2024-02-25 开发教程 Rust 语言中文版 匿名 1

基准测试

Rust支持基准测试来测试用户代码的性能。我们来看一下 src/lib.rs的性能如何。

`#![feature(test)]
extern crate test;
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
use test::Bencher;
#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
#[bench]
fn bench_add_two(b: &mut Bencher) {
b.iter(|| add_two(2));
}
}`

注意上面代码中的 test的功能声明,这表示这并不是一个稳定的功能。

用户需要引入 test的封装,来使得基准测试得以支持。通过 bench属性的使用,我们可以实现一个新的函数。与不能有参数的常规测试不同,这里的基准测试使用 &mut Bencher来改善这个情况。这里的 Bencher提供了一个闭包的 iter 方法。这个闭包就包含了需要进行基准测试的代码。

用户通过 cargo bench指令来执行基准测试:

$ cargo bench
Compiling adder v0.0.1 (file:///home/steve/tmp/adder)
Running target/release/adder-91b3e234d4ed382a
running 2 tests
test tests::it_works ... ignored
test tests::bench_add_two ... bench: 1 ns/iter (+/- 0)
test result: ok. 0 passed; 0 failed; 1 ignored; 1 measured

我们的非基准测试可以被忽略掉。用户也许会注意到 cargo benchcargo test长一个比特。这是因为 Rust 会多次执行基准测试,然后会取一个平均值。因为在这个例子中,只是实现了一些简单的功能,所以我们有一个 ns/iter (+/- 0),但是这会展示出结果的方差。

如下是编写基准测试的建议:

  • 将安装代码移到 iter循环之外,只是将用户希望进行测试的代码放到 iter循环内。
  • 将实现同一功能的代码放到迭代体内,不要将结果进行累计,也不要改变状态。
  • 将外部函数进行幂等化,基准测试程序会将它测试多次。
  • 尽量使 iter循环体内容的代码更精简,然后使得基准测试运行起来更快捷,使得校准器可以以更高精度的来校准。
  • 使 iter循环体内的代码更简单,以帮助查明性能改进的地方(或回归)。

疑难杂症:优化

在基准测试程序的编写方面还存在一些辣手的问题:优化编译后的基准测试代码可能会被优化器篡改,这样使得基准测试可能就不再是用户希望的测试对象了。比如,编译器可能会重新组织一些代码,因为这些代码并没有什么作用,甚至会将它全部删除。

#![feature(test)]
extern crate test;
use test::Bencher;
#[bench]
fn bench_xor_1000_ints(b: &mut Bencher) {
b.iter(|| {
(0..1000).fold(0, |old, new| old ^ new);
});
}

上述代码会出现下述结果:

running 1 test
test bench_xor_1000_ints ... bench: 0 ns/iter (+/- 0)
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured

为了避免上述问题,基准测试的执行者会采用两个方法。iter方法可以获得一个闭包,它可以返回一个任意值来迫使优化器考虑这个结果,来保证优化器不会更改基准测试代码。下面的例子就是来展示如何校准 b.iter.

`b.iter(|| {
// note lack of `;` (could also use an explicit `return`).
(0..1000).fold(0, |old, new| old ^ new)
});`

另一种方法是通用的 test::black_box函数,它对优化器就是一个“黑盒”,可以迫使优化器考虑更多的参数。

#![feature(test)]
extern crate test;
b.iter(|| {
let n = test::black_box(1000);
(0..n).fold(0, |a, b| a ^ b)
})

这些并不会读取和更改这个值。较大的值可以直接降低开销。

`black_box(&huge_struct)`

执行上述任何一种变化提供了以下基准测试结果。

running 1 test
test bench_xor_1000_ints ... bench: 131 ns/iter (+/- 3)
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured

即使使用上述方式,优化器仍然会以不可想象的方式来修改测试用例。