Stable Rustでベンチマークを統計的に評価する

Nightly Rustで有効化される[test crate][test]には[ベンチマーク][bench]を取る機能があります。

[test]: https://doc.rust-lang.org/unstable-book/library-features/test.html
[bench]: https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/benchmark-tests.html

```rust
#![feature(test)]

extern crate test;

pub fn add_two(a: i32) -> i32 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;
    use test::Bencher;

    #[bench]
    fn bench_add_two(b: &mut Bencher) {
        b.iter(|| add_two(2));
    }
}
```
（[ベンチマークテスト][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`を実行するとベンチマークテストを実行してくれます。早すぎてベンチマークが上手くとれない場合は自動的に何回か実行して平均値を出してくれます。

しかしプログラムの実行では様々な要因で速度が変化するため、例えば`master`ブランチから何か高速化を行ったブランチ`feature/faster`でのベンチマーク結果が偶然早かっただけなのか、有意に性能向上が行われたのかを評価する必要があります

criterion.rs
---------------
それを自動的に行ってくれるのが[bheisler/criterion.rs](https://github.com/bheisler/criterion.rs)です。これは[test][test] crateと違ってStable Rustで動作します。


```toml:Cargo.toml
[dev-dependencies]
criterion = "0.3"

[[bench]]
name = "my_benchmark"
harness = false
```

`cargo bench`コマンドには`Cargo.toml`にある`[[bench]]`の設定を見てベンチマークを実行する機能があります。

> Setting the keys autobins, autoexamples, autotests, or autobenches to false in the [package] section will disable auto-discovery of the corresponding target type.
https://doc.rust-lang.org/cargo/reference/manifest.html#target-auto-discovery

ちなみに自動検出機能は無効化する事も出来ます。`harness = false`はテスト用のランタイムを使わない設定で、`criterion`が代わりに実行機能を提供してくれます。`[[bench]]`のように２重括弧になってるのはTOMLのテーブル記法で、JSONで言うと

```json
{
  "bench": [
    { "name": "my_benchmark", "harness": false }
  ]
}
```
のような意味になります。これで`benches/my_benchmark.rs`を実行してくれます。

```rust:benches/my_benchmark.rs
#[macro_use]
extern crate criterion;

use criterion::Criterion;
use criterion::black_box;

fn fibonacci(n: u64) -> u64 {
    match n {
        0 => 1,
        1 => 1,
        n => fibonacci(n-1) + fibonacci(n-2),
    }
}

fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
```

[test][test] crateを意識して作ってるので使い方は概ね分かると思います。詳しくは[ドキュメント](https://docs.rs/crate/criterion/0.3.0)を見てください。

```
$ cargo bench
    Finished release [optimized] target(s) in 0.02s
     Running target/release/deps/bench_cri-5f525a32c3c684a8

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/release/deps/my_benchmark-37b634fc2faf7ab4
fib 20                  time:   [19.834 us 19.894 us 19.951 us]
                        change: [+0.7446% +1.9224% +3.1291%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low mild
  3 (3.00%) high mild
  1 (1.00%) high severe
```

また同時に `target/criterion` 以下にHTMLのレポートが生成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30426/fa8bc001-a03c-803c-d331-1e0495052388.png)