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 のベンチマークツールはこういった条件を極力簡単に満たせる為の仕組みやツールが揃っています。ぜひ有効に活用して下さい。