Goでベンチマーク!パフォーマンス改善のための第一歩
概要
Goを書いていると「この処理は遅いのか?速いのか?」と気になることが増えてきます。
そんなとき便利なのが Goの ベンチマーク機能 です。
今回は基本の使い方+よくある用途+便利なオプション に加え、
Mutex と Channel のパフォーマンス比較という実務にも役立つ例をご紹介します。
ベンチマークをやる理由
コードのパフォーマンス改善に取り組むとき、
「体感で遅い」「たぶんこっちが速そう」という思い込みにハマりがちです。
でもそれは危険。
💬 Rob Pike氏(Goの開発者の一人)の有名な言葉:
"Don’t tune for speed until you’ve measured, and even then don’t unless one part of the code overwhelms the rest."
👉「推測するな、計測せよ。」
つまり、実際に測ってから判断しろ ということです。
Goのベンチマークの使い方
Goの標準パッケージ testing
には、ベンチマーク用の仕組みが組み込まれています。
- ✅ 特別なツールは不要
- ✅ 通常の
go test
コマンドで実行可能
ベンチマーク用の関数を用意しよう
func BenchmarkXxx(b *testing.B) {
for i := 0; i < b.N; i++ {
// 測定したい処理
}
}
💡 詳細
- 関数名は
Benchmark
プレフィックスが必須- 引数は
*testing.B
型b.N
回ループして測定する(Goが自動調整)※
testing.B
の詳細はこちら
benchmark.go
具体例:スライスのAppend vs Copy
package mypackage
import "testing"
func BenchmarkAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
s := []int{}
s = append(s, 1, 2, 3, 4, 5)
}
}
func BenchmarkCopy(b *testing.B) {
for i := 0; i < b.N; i++ {
src := []int{1, 2, 3, 4, 5}
dst := make([]int, len(src))
copy(dst, src)
}
}
ベンチマークの実行方法
$ go test -bench=.
🧾 詳細
💡 詳細
-bench=. → すべての Benchmark 関数を実行
go test のサブコマンドとして動作
結果例
BenchmarkAppend-10 10000000 120 ns/op
BenchmarkCopy-10 20000000 80 ns/op
🧾 詳細
💡 詳細
何回ループしたか → 10000000
1回あたりの実行時間 → 120ns/op
用途例
- アルゴリズムの比較(map vs slice など)
- 圧縮/暗号/シリアライズなどの高速化
- MutexとChannelの比較(ロック vs メッセージパッシング)
- JSONライブラリの比較(標準 vs 高速版)
- I/O 処理のオーバーヘッド確認
👉 ボトルネック特定にも使える
オプション例
オプション | 内容 |
---|---|
-bench=Regexp |
実行するベンチマークの名前指定 |
-benchmem |
メモリアロケーション数 / メモリ使用量も表示 |
-count=N |
N回繰り返して平均を取る(ノイズ低減) |
例:メモリ消費も確認
$ go test -bench=. -benchmem
出力例:
BenchmarkAppend-10 10000000 120 ns/op 64 B/op 1 allocs/op
👉 B/op(1回の操作で何バイト確保するか)
👉 allocs/op(何回メモリ割当が発生したか)
Mutex vs Channel ベンチマーク例
ここが実務で とても役立つ! 例です。
同期処理に sync.Mutex を使うのと Channelを使うのとでは
パフォーマンスに大きな差が出ることが多いです。
ぜひ試してみましょう!
package mypackage
import (
"sync"
"testing"
)
func BenchmarkMutex(b *testing.B) {
var mu sync.Mutex
var counter int
for i := 0; i < b.N; i++ {
mu.Lock()
counter++
mu.Unlock()
}
}
func BenchmarkChannel(b *testing.B) {
ch := make(chan int, 1)
ch <- 0
for i := 0; i < b.N; i++ {
val := <-ch
val++
ch <- val
}
}
実行結果例
私の環境で試すと以下の結果になりました。
BenchmarkMutex-8 510459456 2.255 ns/op
BenchmarkChannel-8 68742958 19.21 ns/op
🧾 詳細
💡 Mutex
約5億回ループ
1回あたり 2.255ナノ秒
💡 Channel
約6800万回ループ
1回あたり 19.21ナノ秒
結果考察
- 速さに関してはMutexに軍配
- Channelは「goroutine間通信」「柔軟性」「安全性」はあるが、コストが大きい
- 高頻度のロック操作にはMutexのほうが向いてそう
まとめ
- Goのtestingパッケージはベンチマークが超簡単
- 「推測するな、計測せよ」 → まずは測ってからチューニング
- MutexとChannelでは明確な速度差がある → 用途に応じて選択
- -benchmem や -count オプションを活用しよう
おまけ:もうひとつの格言
"First make it correct, then make it fast."
「まず正しく書け。速さはそのあとだ。」
Discussion