2016/10/19

golang のテストツールには標準でベンチマークツールが付属しています。例えば、引数 n を貰ってその数分だけメッセージの入ったスライスを返す関数 makeSlice が以下の実装だったとします。

foo.go

package foo

import "fmt"

func makeSlice(n int) []string {
    var r []string
    for i := 0; i < n; i++ {
        r = append(r, fmt.Sprintf("%03d だよーん", i))
    }
    return r
}

如何にも遅そうなコードですね。まずはこのコードを単品で計測するベンチマークを書きます。

foo_test.go

package foo

import "testing"

func BenchmarkMakeSlice(b *testing.B) {
    b.ResetTimer()
    makeSlice(b.N)
}

このベンチマークを実行する為には以下の様に実行します。

$ go test -test.bench BenchmarkMakeSlice

この時、気を付ける事は makeSlice に与える負荷量を固定値にしない事です。ベンチマークの実行は通常、b.N に対して 100, 10000, 1000000, 5000000 のベンチマークが実行されます。b.N の値の変化により処理がどの様に遅くなるかを検出する為には b.N に依存した処理を書く必要があります。

次に気を付けるべき事は、-count を指定する事です。b.N に対するテストを1回ずつ行ったとしても安定していないならば参考値にしかなり得ないからです。以下の様に -count を指定して実行します。

$ go test -count 10 -test.bench BenchmarkMakeSlice
BenchmarkMakeSlice-4     5000000               355 ns/op
BenchmarkMakeSlice-4     5000000               376 ns/op
BenchmarkMakeSlice-4     5000000               377 ns/op
BenchmarkMakeSlice-4     5000000               390 ns/op
BenchmarkMakeSlice-4     5000000               359 ns/op
BenchmarkMakeSlice-4     5000000               342 ns/op
BenchmarkMakeSlice-4     5000000               400 ns/op
BenchmarkMakeSlice-4     3000000               384 ns/op
BenchmarkMakeSlice-4     3000000               335 ns/op
BenchmarkMakeSlice-4     5000000               377 ns/op
PASS
ok      _/C_/dev/go-sandbox/bench       22.062s

ここまで出来たら改良したソースコードとの比較を始めましょう。改良された後のベンチマークだけを見たとしても本当に速くなったのかどうかは断言できませんよね。改良は上記の makeSlice を以下の様に改良しました。

package foo

import "fmt"

func makeSlice(n int) []string {
    r := make([]string, n)
    for i := 0; i < n; i++ {
        r[i] = fmt.Sprintf("%03d だよーん", i)
    }
    return r
}

make により予めスライスを確保する事で、メモリ確保の回数を減らしています。改良後のベンチマークを取ります。

$ go test -count 10 -test.bench BenchmarkMakeSlice
BenchmarkMakeSlice-4    10000000           162 ns/op
BenchmarkMakeSlice-4    10000000           161 ns/op
BenchmarkMakeSlice-4    10000000           160 ns/op
BenchmarkMakeSlice-4    10000000           161 ns/op
BenchmarkMakeSlice-4    10000000           163 ns/op
BenchmarkMakeSlice-4    10000000           165 ns/op
BenchmarkMakeSlice-4    10000000           166 ns/op
BenchmarkMakeSlice-4    10000000           159 ns/op
BenchmarkMakeSlice-4    10000000           162 ns/op
BenchmarkMakeSlice-4    10000000           157 ns/op
PASS
ok      _/C_/dev/go-sandbox/bench   18.430s

さて、確かに目に見えて速くなってはいるのですが、いったいどの程度速くなっているのでしょうか。-count を指定した事でこの処理には若干ながら揺らぎがある事が見えます。その揺らぎを纏めて、どの程度高速化されたのかを知りたいですよね。そこで便利なのが benchstat です。

benchstat - GoDoc

Command benchstat Benchstat computes and compares statistics about benchmarks. Usage: benchstat [-de...

https://godoc.org/rsc.io/benchstat

benchstat はベンチマーク結果の前後を比較し、揺らぎを計算した上でどの程度の速度差があるかを表示できるツールです。インストールは以下の様に行います。

$ go get rsc.io/benchstat

改良前のベンチマーク結果を bench1.log というファイルに、改良後のベンチマーク結果を bench2.log というファイルに出力させた後、以下の様に実行します。

$ benchstat bench1.log bench2.log
name         old time/op  new time/op  delta
MakeSlice-4   369ns ± 9%   162ns ± 3%  -56.26%  (p=0.000 n=9+10)

揺らぎが纏められ、この改良で処理時間が改良前の 56.26% にまで減っている事が分かりました。

  • 改修の前後で同じ条件である事
  • 改修内容に対して網羅的な入力パターン
  • 揺らぎが発生する程に多い回数

ベンチマークはこれらの条件を満たさないと本当に正しい結果は得られないと思います。golang のベンチマークツールはこういった条件を極力簡単に満たせる為の仕組みやツールが揃っています。ぜひ有効に活用して下さい。

Posted at 12:55 | WriteBacks () | Edit
Edit this entry...

wikieditish message: Ready to edit this entry.






















A quick preview will be rendered here when you click "Preview" button.