# 基準測試
> [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.參考文獻
- 附錄:名詞中英文對照