# 基准测试
> [benchmark-tests.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/benchmark-tests.md)
commit 024aa9a345e92aa1926517c4d9b16bd83e74c10d
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`功能 gate,它启用了这个不稳定功能。
我们导入了`test`crate,它包含了对基准测试的支持。我们也定义了一个新函数,带有`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 bench`比`cargo test`花费的时间更长。这是因为Rust会多次运行我们的基准测试,然后取得平均值。因为我们的函数只做了非常少的操作,我们耗费了`1 ns/iter (+/- 0)`,不过运行时间更长的测试就会有出现偏差。
编写基准测试的建议:
- 把初始代码放于`iter`循环之外,只把你想要测试的部分放入它
- 确保每次循环都做了“同样的事情”,不要累加或者改变状态
- 确保外边的函数也是幂等的(idempotent),基准测试runner可能会多次运行它
- 确保`iter`循环内简短而快速,这样基准测试会运行的很快同时校准器可以在合适的分辨率上调整运转周期
- 确保`iter`循环执行简单的工作,这样可以帮助我们准确的定位性能优化(或不足)
# Gocha:优化
写基准测试有另一些比较微妙的地方:开启了优化编译的基准测试可能被优化器戏剧性的修改导致它不再是我们期望的基准测试了。举例来说,编译器可能认为一些计算并无外部影响并且整个移除它们。
~~~
#![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`调用:
~~~
# struct X;
# impl X { fn iter<T, F>(&self, _: F) where F: FnMut() -> T {} } let b = X;
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;
# fn main() {
# struct X;
# impl X { fn iter<T, F>(&self, _: F) where F: FnMut() -> T {} } let b = X;
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
~~~
然而,即使使用了上述方法优化器还是可能在不合适的情况下修改测试用例。
- 前言
- 贡献者
- 1.介绍
- 2.准备
- 3.学习 Rust
- 3.1.猜猜看
- 3.2.哲学家就餐问题
- 3.3.其它语言中的 Rust
- 4.语法和语义
- 4.1.变量绑定
- 4.2.函数
- 4.3.原生类型
- 4.4.注释
- 4.5.If语句
- 4.6.循环
- 4.7.所有权
- 4.8.引用和借用
- 4.9.生命周期
- 4.10.可变性
- 4.11.结构体
- 4.12.枚举
- 4.13.匹配
- 4.14.模式
- 4.15.方法语法
- 4.16.Vectors
- 4.17.字符串
- 4.18.泛型
- 4.19.Traits
- 4.20.Drop
- 4.21.if let
- 4.22.trait 对象
- 4.23.闭包
- 4.24.通用函数调用语法
- 4.25.crate 和模块
- 4.26.const和static
- 4.27.属性
- 4.28.type别名
- 4.29.类型转换
- 4.30.关联类型
- 4.31.不定长类型
- 4.32.运算符和重载
- 4.33.Deref强制多态
- 4.34.宏
- 4.35.裸指针
- 4.36.不安全代码
- 5.高效 Rust
- 5.1.栈和堆
- 5.2.测试
- 5.3.条件编译
- 5.4.文档
- 5.5.迭代器
- 5.6.并发
- 5.7.错误处理
- 5.8.选择你的保证
- 5.9.外部函数接口
- 5.10.Borrow 和 AsRef
- 5.11.发布途径
- 5.12.不使用标准库
- 6.Rust 开发版
- 6.1.编译器插件
- 6.2.内联汇编
- 6.4.固有功能
- 6.5.语言项
- 6.6.链接进阶
- 6.7.基准测试
- 6.8.装箱语法和模式
- 6.9.切片模式
- 6.10.关联常量
- 6.11.自定义内存分配器
- 7.词汇表
- 8.语法索引
- 9.参考文献
- 附录:名词中英文对照