導入:なぜ並列テストが重要なのか
Go言語は並列処理が得意な言語ですが、複雑な処理を並列で実行すると、予期せぬ「競合(コンテンション)」が発生することがあります。特に、複数のゴルーチンが同じリソース(メモリやデータベース接続など)を奪い合うと、コア数を増やしたはずなのに逆に処理が遅くなるという現象が起きます。この課題を解決し、プログラムがマルチコア環境で正しくスケールするかを検証するために、Goのテストツールが備えている「-cpu」フラグを活用しましょう。
基礎知識:-cpuフラグとは何か
Goのテストコマンドで使用できる「-cpu」フラグは、テスト実行時に使用するCPUコア数を指定するものです。例えば「go test -cpu=1,2,4,8」と入力すると、Goは指定された数(1コア、2コア、4コア、8コア)の順にテストを自動で実行してくれます。これにより、コア数が増えるにつれてテスト時間がどう変化するかを簡単に比較できます。
実装・解決策:ボトルネックの特定
通常、テストは現在のCPUコア数で実行されますが、これだけでは「ロック競合」に気づくことは困難です。以下の手順で検証を行います。
1. 処理が重い、または並列処理を含むパッケージでテストを実行する。
2. -cpuフラグに複数の数値をカンマ区切りで渡す。
3. 各コア数での実行時間(duration)を比較し、コア数が増えても時間が短縮されない、あるいは悪化している箇所を探す。
サンプルプログラム
以下のコードは、排他制御(Mutex)がボトルネックになりやすい状況を想定したテストコードです。これを「main_test.go」として保存し、実際に試してみてください。
package main
import (
"sync"
"testing"
)
// 共有リソースへのアクセスを模倣する関数
var counter int
var mu sync.Mutex
func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}
// 並列で実行されるテストケース
func TestParallelIncrement(t testing.T) {
// 1000回並列でインクリメントを行う
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
}
// 実行方法:
// go test -v -cpu=1,2,4,8 main_test.go
// 実行結果で、CPU数が増えた時に時間が大幅に増えていないかを確認してください。
応用・注意点:現場での活用と回避策
現場でこの手法を使う際のポイントは「期待値とのギャップ」を見ることです。理想的にはコア数が増えるほど処理速度は向上しますが、ロック競合が激しいコードでは、逆にパフォーマンスが著しく低下します。
注意すべき点として、テスト環境(CIサーバーなど)のCPU数が制限されている場合、-cpu=8のように指定しても実際の物理コア数以上の効果は出ません。また、計測時は「go test -bench=. -cpu=1,2,4,8」のようにベンチマークと組み合わせることで、より正確なスケーラビリティの検証が可能になります。競合が見つかった場合は、ロックの粒度を細かくする、あるいはチャネルを使った設計への変更を検討しましょう。

コメント