ベンチマークの書き方#

ベンチマークは、コードの性能を測定する手段です。異なる実装を比較したり、時間の経過に伴う性能変化を追跡したりするために使えます。

test ブロックによるベンチマーク#

関数をベンチマークする最も簡単な方法は、@bench.T 引数付きの test ブロックを使うことです。@bench.T::bench メソッドは () -> Unit 型の関数を受け取り、適切な回数だけ実行します。計測と統計解析は自動で行われ、moon に渡されてコンソール出力に表示されます。

fn fib(n : Int) -> Int {
  if n < 2 {
    return n
  }
  return fib(n - 1) + fib(n - 2)
}

test (b : @bench.T) {
  b.bench(fn() { b.keep(fib(20)) })
}

出力は次のとおりです:

time (mean ± σ)         range (min … max) 
  21.67 µs ±   0.54 µs    21.28 µs …  23.14 µs  in 10 ×   4619 runs

関数は 10 × 4619 回実行されます。2 つ目の数値はベンチマークユーティリティによって自動的に決定されます。これは、正確な計測のために十分長い時間になるまで反復回数を増やしていくためです。1 つ目の数値は、@bench.T::bench 引数に名前付き引数 count を渡すことで調整できます。

test (b : @bench.T) {
  b.bench(fn() { b.keep(fib(20)) }, count=20)
}

@bench.T::keep は重要な補助関数で、計算が最適化されて丸ごと省略されるのを防ぎます。純粋関数をベンチマークする場合は、最適化の影響を避けるために必ずこの関数を使ってください。ただし、コンパイラが事前計算して定数に置き換える可能性は依然としてあります。

まとめてベンチマークする#

ベンチマークのよくある用途は、同じ関数の 2 つ以上の実装を比較することです。この場合、比較しやすいように、ブロック内でまとめてベンチマークするとよいでしょう。@bench.T::bench メソッドの name 引数は、各ベンチマークを識別するために使えます。

fn fast_fib(n : Int) -> Int {
  if n < 2 {
    return n
  } else {
    let mut a = 0
    let mut b = 1
    for i = 2; i <= n; i = i + 1 {
      let t = a + b
      a = b
      b = t
    }
    b
  }
}

test (b : @bench.T) {
  b.bench(name="naive_fib", fn() { b.keep(fib(20)) })
  b.bench(name="fast_fib", fn() { b.keep(fast_fib(20)) })
}

これで、出力を見ればどちらが速いか判断できます:

name      time (mean ± σ)         range (min … max) 
naive_fib   21.01 µs ±   0.21 µs    20.76 µs …  21.32 µs  in 10 ×   4632 runs
fast_fib     0.02 µs ±   0.00 µs     0.02 µs …   0.02 µs  in 10 × 100000 runs

生のベンチマーク統計#

さらに分析するために、生のベンチマーク統計を取得したい場合があります。@bench.single_bench 関数は抽象型 Summary を返し、JSON 形式にシリアライズできます。Summary 型の安定性は保証されていません。

この場合、計算が最適化で消されないように注意する必要があります。keep 関数を単独の関数として使うことはできません。これは @bench.T のメソッドです。

fn collect_bench() -> Unit {
  let mut saved = 0
  let summary : @bench.Summary = @bench.single_bench(name="fib", fn() {
    saved = fib(20)
  })
  println(saved)
  println(summary.to_json().stringify(escape_slash=true, indent=4))
}

出力は次のようになるかもしれません:

6765
{
    "name": "fib",
    "sum": 217.22039973878972,
    "min": 21.62009230518067,
    "max": 21.87286402916848,
    "mean": 21.72203997387897,
    "median": 21.70412048323901,
    "var": 0.007197724461032505,
    "std_dev": 0.08483940394081341,
    "std_dev_pct": 0.39056830777787843,
    "median_abs_dev": 0.08189815918589166,
    "median_abs_dev_pct": 0.3773392211360855,
    "quartiles": [
        21.669052078798433,
        21.70412048323901,
        21.76141434479756
    ],
    "iqr": 0.09236226599912811,
    "batch_size": 4594,
    "runs": 10
}

時間の単位はマイクロ秒です。