はじめに
golangのtest全般を支援しているtestingパッケージには関数のベンチマークを行う機能が備わっています。
今回はそれを用いてベンチマークを取って行きます。
ベンチマークをする
まずベンチマークの対象となる実装をしていきます。
メモリ確保を先にしておくとパフォーマンスが上がるコードを例としてみます。
ベンチマークを取る対象の関数を実装
引数nを貰ってその数だけのメッセージが入ったスライスを返却し
都度appendする makeSlice1
と
先にメモリを確保する makeSlice2
の二つ関数を実装します。
hoge_test.go
package main
import (
"fmt"
"testing"
)
func makeSlice1(n int) []string {
var r []string
for i := 0; i < n; i++ {
// 都度appendする
r = append(r, fmt.Sprintf("%03d hoge", i))
}
return r
}
func makeSlice2(n int) []string {
// 長さを指定してメモリを確保する
r := make([]string, n)
for i := 0; i < n; i++ {
r[i] = fmt.Sprintf("%03d hoge", i)
}
return r
}
ベンチマーク関数の実装
次にベンチマーク関数の実装を行います。
hoge_test.go
func BenchmarkMakeSlice1(b *testing.B) {
b.ResetTimer()
makeSlice1(b.N)
}
func BenchmarkMakeSlice2(b *testing.B) {
b.ResetTimer()
makeSlice2(b.N)
}
b.ResetTimer()
func (b *B) ResetTimer()
はタイマーを停止し、経過したベンチマーク時間とメモリ割り当てのカウンタをゼロにします
b.N
ベンチマークが実行されながら、実行時間(所要時間)を正しく測定できるまで、b.Nの値が調整される。
ベンチマークの実行
$ go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/go_app/test
BenchmarkMakeSlice1-4 5000000 311 ns/op
BenchmarkMakeSlice2-4 10000000 196 ns/op
PASS
ok github.com/go_app/test 4.379s
// テストした関数の実行回数
5000000
// 1回あたりの実行に掛かった時間(ns/op)
311 ns/op
nanosecond/operation
メモリの確保した容量も気になりますよね。
-benchmemオプションを付けると
$ go test -bench . -benchmem
BenchmarkMakeSlice1-4 5000000 294 ns/op 108 B/op 2 allocs/op
BenchmarkMakeSlice2-4 10000000 148 ns/op 40 B/op 2 allocs/op
PASS
ok github.com/go_app/test 3.468s
// 実行ごとに割り当てられたメモリのサイズ
108 B/op
(Byts/operation)
// 1回の実行でメモリアロケーションが行われた回数
2 allocs/op
上記のベンチマークの結果により、予めスライスを確保する事で、メモリ確保の回数を減らした方がパフォーマンスが良いことがわかりました。
おまけ
Go 1.12から実行回数を指定してベンチマークを取ることができるようになりました。
$ go test -bench . -benchtime=1000x
BenchmarkMakeSlice1-4 1000 376 ns/op
BenchmarkMakeSlice2-4 1000 178 ns/op
PASS