Go でベンチマーク
Intro
Go にはベンチマークを取る仕組みが標準で備わっています。
テストを書く仕組みも標準で備わっており、
testing モジュールと go test コマンドで行いますが、
ベンチも同じような形で実行することができます。
今回は簡単なベンチ取り方を紹介します。
テストについては、そのうちちゃんと触れます。
参考ソースとして、こちらを使います。
対象のコード
例えば以下のようなコードがあって、そのベンチを取るとします。
// swrap.go type SWrap []byte func (sw *SWrap) Add(a byte) { *sw = append(*sw, a) } func (sw *SWrap) Len() int { return len(*sw) }
Slice にメソッドを生やしただけです。
ベンチマーク関数
ベンチマークは、 *_test.go ファイルに書きます。
関数名は Bench で始め、引数として *testing.B を渡す関数として定義します。
(このへんはテストと同じです。)
// swrap_test.go import ( "testing" ) func BenchmarkAdd(b *testing.B) { for i := 0; i < b.N; i++ { sw := New(Fixture()) sw.Add(0xFF) } } func BenchmarkLen(b *testing.B) { sw := New(Fixture()) b.ResetTimer() for i := 0; i < b.N; i++ { sw.Len() } }
b.N が勝手に設定されるので、それをループの条件とするだけです。
ループにかかる時間を測るので、ループに入る前の準備処理などに時間がかかる場合は、
ループの前に b.ResetTimer() を呼ぶと準備処理分を引くことができます。
実行方法
実行は go test にオプションを追加するだけです。($GOPATH を通しておいて下さい)
$ go test -bench Add # Add とつくものだけ実行 $ go test -bench . # 全て実行
全ての実行例はこんな感じです。(インデントだけ整形済み)
$ go test -bench . PASS BenchmarkNew 2000000000 0.64 ns/op BenchmarkBytes 2000000000 1.09 ns/op BenchmarkLen 2000000000 1.23 ns/op BenchmarkAdd 5000000 334 ns/op BenchmarkMerge 10000000 254 ns/op BenchmarkDelete 20000000 148 ns/op BenchmarkCompare 50000000 32.8 ns/op BenchmarkPush 5000000 354 ns/op BenchmarkPop 10000000 114 ns/op BenchmarkShift 10000000 234 ns/op BenchmarkUnShift 20000000 109 ns/op BenchmarkReplace 5000000 614 ns/op ok swrap 27.847s
実行回数が違うのは、 b.N の値が変わっているからですね。
この b.N は直接指定するのではなく、ベンチの実行時間からの逆算です。
デフォルトは 1 秒間実行しているようです。
この実行時間は -benchtime で指定できます。
十分な時間を指定し、性能自体は ns/op で見るのが良さそうです。
benchmem
"-benchmem" オプションをつけるとメモリアロケート回数がわかります。
変数をポインタに変更するなどの改善ポイントが見つけられます。
PASS BenchmarkLen 2000000000 0.95 ns/op 0 B/op 0 allocs/op BenchmarkAdd 5000000 370 ns/op 48 B/op 2 allocs/op
比較
実はこの結果を比較するツールがひっそりと同梱されています。
まったくいいサンプルが無いんだけど手元にあったもので、
例えば以下の様な超地味なチューニングに成功したとします。
PASS BenchmarkNew 2000000000 0.47 ns/op 0 B/op 0 allocs/op ok swrap 1.000s
PASS BenchmarkNew 2000000000 0.44 ns/op 0 B/op 0 allocs/op ok swrap 0.954s
それぞれ before after で保存して
これを misc 以下にある benchcmp に渡します。
$ $GOROOT/misc/benchcmp before after benchmark old ns/op new ns/op delta BenchmarkNew 0 0 +6.82%
こうした値をパッチに添えて PR とかすると話が速くていいかもしれませんね。
オプション
オプションを簡単(適当)に訳しておきます。
(まだ使ったことがないものが多いです)
http://golang.org/cmd/go/#hdr-Description_of_testing_flags
-bench regexp 対象の指定 -benchmem メモリアロケーションの表示 -benchtime ベンチ実行時間 -blockprofile goroutine のブロックプロファイルを出力 -blockprofilerate runtime.SetBlockProfileRate の値を指定 -cpu GOMAXPROCS の値を指定 -cpuprofile CPU プロファイルを出力 -memprofile メモリプロファイルを出力 -memprofilerate runtime.MemProfileRate の値を指定 -parallel 並列実行の指定(デフォルトは GOMAXPROCS) -run テストと example の実行対象指定 -short 時間を短縮して簡易実行 -timeout タイムアウト -v 詳細出力