benchmark-tests.md
commit 024aa9a345e92aa1926517c4d9b16bd83e74c10d
Rust 也支持基準(zhǔn)測試,它可以測試代碼的性能。讓我們把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,它啟用了這個不穩(wěn)定功能。
我們導(dǎo)入了test
crate,它包含了對基準(zhǔn)測試的支持。我們也定義了一個新函數(shù),帶有bench
屬性。與一般的不帶參數(shù)的測試不同,基準(zhǔn)測試有一個&mut Bencher
參數(shù)。Bencher
提供了一個iter
方法,它接收一個閉包。這個閉包包含我們想要測試的代碼。
我們可以用cargo bench
來運行基準(zhǔn)測試:
$ 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
我們的非基準(zhǔn)測試將被忽略。你也許會發(fā)現(xiàn)cargo bench
比cargo test
花費的時間更長。這是因為Rust會多次運行我們的基準(zhǔn)測試,然后取得平均值。因為我們的函數(shù)只做了非常少的操作,我們耗費了1 ns/iter (+/- 0)
,不過運行時間更長的測試就會有出現(xiàn)偏差。
編寫基準(zhǔn)測試的建議:
iter
循環(huán)之外,只把你想要測試的部分放入它iter
循環(huán)內(nèi)簡短而快速,這樣基準(zhǔn)測試會運行的很快同時校準(zhǔn)器可以在合適的分辨率上調(diào)整運轉(zhuǎn)周期iter
循環(huán)執(zhí)行簡單的工作,這樣可以幫助我們準(zhǔn)確的定位性能優(yōu)化(或不足)寫基準(zhǔn)測試有另一些比較微妙的地方:開啟了優(yōu)化編譯的基準(zhǔn)測試可能被優(yōu)化器戲劇性的修改導(dǎo)致它不再是我們期望的基準(zhǔn)測試了。舉例來說,編譯器可能認(rèn)為一些計算并無外部影響并且整個移除它們。
#![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);
});
}
得到如下結(jié)果:
running 1 test
test bench_xor_1000_ints ... bench: 0 ns/iter (+/- 0)
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured
基準(zhǔn)測試運行器提供兩種方法來避免這個問題:要么傳遞給iter
的閉包可以返回一個隨機的值這樣強制優(yōu)化器認(rèn)為結(jié)果有用并確保它不會移除整個計算部分。這可以通過修改上面例子中的b.iter
調(diào)用:
# 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)
});
要么,另一個選擇是調(diào)用通用的test::black_box
函數(shù),它會傳遞給優(yōu)化器一個不透明的“黑盒”這樣強制它考慮任何它接收到的參數(shù)。
#![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)
)。
執(zhí)行上面任何一種修改可以獲得如下基準(zhǔn)測試結(jié)果:
running 1 test
test bench_xor_1000_ints ... bench: 131 ns/iter (+/- 3)
test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured
然而,即使使用了上述方法優(yōu)化器還是可能在不合適的情況下修改測試用例。