[インデックス 18619] ファイルの概要
このコミットは、Go言語の標準ライブラリtime
パッケージ内のベンチマークテストsleep_test.go
において、ベンチマークの実行方法をtesting.B.RunParallel
関数を使用するように変更するものです。これにより、並行処理のベンチマークがより効率的かつ正確に行えるようになります。
コミット
time: use RunParallel in benchmarks
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/68060043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/bb9531e11ba964fa5f8df2404fea8606a4a43b1d
元コミット内容
src/pkg/time/sleep_test.go
ファイルにおいて、benchmark
関数内のベンチマーク実行ロジックが変更されました。
変更前は、runtime.GOMAXPROCS(-1)
で取得した論理CPUの数に応じてP
個のgoroutineを起動し、sync.WaitGroup
とatomic.AddInt32
を用いてb.N
回分のベンチマーク処理を並行して実行していました。具体的には、b.N
をbatch
サイズで分割し、各goroutineがatomic.AddInt32(&N, -1) >= 0
の条件が満たされるまでbench(batch)
を呼び出す形式でした。
変更の背景
Goのtesting
パッケージには、並行ベンチマークをより簡単かつ効率的に記述するためのtesting.B.RunParallel
関数が用意されています。このコミットは、既存の手動でgoroutineを管理し、sync.WaitGroup
やatomic
操作を用いて並行処理を行うベンチマークコードを、testing.B.RunParallel
を使用する形に置き換えることを目的としています。
RunParallel
を使用することで、ベンチマークコードが簡潔になり、Goのテストフレームワークが提供する並行処理の管理機能(goroutineの生成、イテレーションの分散、タイミング計測など)を最大限に活用できるようになります。これにより、ベンチマークの信頼性と保守性が向上します。
前提知識の解説
testing.B.RunParallel
testing.B.RunParallel
は、Goのtesting
パッケージが提供するベンチマーク関数で、並行処理のベンチマークを行うために使用されます。この関数は、GOMAXPROCS
(またはb.SetParallelism
で設定された値)に基づいて複数のgoroutineを起動し、b.N
で指定されたイテレーション数をこれらのgoroutineに分散して実行させます。
RunParallel
に渡される関数は*testing.PB
型の引数を受け取ります。このpb
オブジェクトのpb.Next()
メソッドは、そのgoroutineに割り当てられたイテレーションが残っている限りtrue
を返します。ベンチマークコードはfor pb.Next() { ... }
のループ内で記述されます。
runtime.GOMAXPROCS
runtime.GOMAXPROCS
は、Goランタイムが同時に実行できるOSスレッドの最大数を設定または取得する関数です。引数に-1
を渡すと、現在のGOMAXPROCS
の値を変更せずに取得できます。これは、Goプログラムが利用できる論理CPUコアの数に相当し、並行処理のベンチマークにおいて、いくつの並行実行単位をシミュレートするかを決定する際に重要な要素となります。
sync.WaitGroup
sync.WaitGroup
は、複数のgoroutineの完了を待つための同期プリミティブです。Add
メソッドで待機するgoroutineの数を設定し、各goroutineが完了する際にDone
メソッドを呼び出します。Wait
メソッドは、すべてのgoroutineがDone
を呼び出すまでブロックします。変更前のコードでは、手動で起動したgoroutineの完了を待つために使用されていました。
atomic.AddInt32
sync/atomic
パッケージは、アトミックな(不可分な)操作を提供します。atomic.AddInt32
は、int32
型の変数に指定された値をアトミックに加算し、その結果を返します。これは、複数のgoroutineから共有されるカウンタなどを安全に更新するために使用されます。変更前のコードでは、b.N
のイテレーション数を複数のgoroutineで共有し、各goroutineが処理するたびにデクリメントするために使用されていました。
testing.PB
testing.PB
はtesting.B.RunParallel
に渡される関数に提供されるオブジェクトで、並行ベンチマークのイテレーション管理を行います。pb.Next()
メソッドを通じて、各並行実行単位が次に実行すべきイテレーションがあるかどうかを判断します。
技術的詳細
このコミットの技術的詳細は、Goのベンチマークにおける並行処理の管理方法の進化を示しています。
変更前のアプローチ:
変更前のコードは、GOMAXPROCS
の数だけgoroutineを明示的に起動し、sync.WaitGroup
とatomic.AddInt32
を組み合わせて、b.N
回のベンチマークイテレーションをこれらのgoroutineに手動で分散させていました。
P := runtime.GOMAXPROCS(-1)
: 利用可能な論理CPU数を取得。N := int32(b.N / batch)
: 全イテレーション数をバッチサイズで割った回数を計算。for p := 0; p < P; p++ { go func() { ... } }
:P
個のgoroutineを起動。for atomic.AddInt32(&N, -1) >= 0 { bench(batch) }
: 各goroutineがN
が0になるまでバッチ処理を実行。atomic.AddInt32
でN
を安全にデクリメント。wg.Wait()
: 全てのgoroutineの完了を待機。
このアプローチは機能しますが、開発者が手動でgoroutineの生成、イテレーションの割り当て、同期を管理する必要があり、コードが複雑になりがちです。また、batch
サイズの設定やb.N
の分割方法によっては、ベンチマークの精度や効率に影響を与える可能性がありました。
変更後のアプローチ (testing.B.RunParallel
の使用):
testing.B.RunParallel
を使用することで、これらの複雑な並行処理の管理がGoのテストフレームワークに委ねられます。
b.RunParallel(func(pb *testing.PB) { ... })
:RunParallel
を呼び出し、並行実行されるベンチマークロジックを匿名関数として渡します。for pb.Next() { bench(1000) }
:pb.Next()
がtrue
を返す限り、ベンチマーク対象の関数bench(1000)
が実行されます。RunParallel
は、b.N
で指定された総イテレーション数を、内部で起動した複数のgoroutineに自動的に分散します。各goroutineは、自身の担当するイテレーションが完了するまでpb.Next()
を呼び出し続けます。
この変更により、以下の利点が得られます。
- コードの簡潔性: 手動でのgoroutine管理、
WaitGroup
、atomic
操作が不要になり、ベンチマークコードが大幅に簡素化されます。 - 信頼性の向上: Goのテストフレームワークが並行処理の管理を最適化するため、手動実装に比べてエラーの発生リスクが低減し、より信頼性の高いベンチマーク結果が得られます。
- 効率性:
RunParallel
は、GOMAXPROCS
やb.SetParallelism
の設定に基づいて最適な数のgoroutineを起動し、イテレーションを効率的に分散します。これにより、並行処理のオーバーヘッドが最小限に抑えられ、より正確なパフォーマンス測定が可能になります。 - 標準化: Goのベンチマークのベストプラクティスに沿った記述となり、他の開発者にとっても理解しやすくなります。
この変更は、Goのベンチマークツールが提供する機能を最大限に活用し、より堅牢で保守性の高いベンチマークコードを実現するための典型的なリファクタリングパターンです。
コアとなるコードの変更箇所
--- a/src/pkg/time/sleep_test.go
+++ b/src/pkg/time/sleep_test.go
@@ -74,26 +74,13 @@ func benchmark(b *testing.B, bench func(n int)) {
for i := 0; i < len(garbage); i++ {\n \t\tgarbage[i] = AfterFunc(Hour, nil)\n \t}\n-\n-\tconst batch = 1000\n-\tP := runtime.GOMAXPROCS(-1)\n-\tN := int32(b.N / batch)\n-\
\tb.ResetTimer()\
\n-\tvar wg sync.WaitGroup\n-\twg.Add(P)\n-\n-\tfor p := 0; p < P; p++ {\n-\t\tgo func() {\n-\t\t\tfor atomic.AddInt32(&N, -1) >= 0 {\n-\t\t\t\tbench(batch)\n-\t\t\t}\n-\t\t\twg.Done()\n-\t\t}()\n-\t}\n-\n-\twg.Wait()\
+\tb.RunParallel(func(pb *testing.PB) {\
+\t\tfor pb.Next() {\
+\t\t\tbench(1000)\
+\t\t}\
+\t})\
\n \tb.StopTimer()\
\tfor i := 0; i < len(garbage); i++ {\
コアとなるコードの解説
変更はsrc/pkg/time/sleep_test.go
ファイルのbenchmark
関数内で行われています。
変更前:
const batch = 1000
P := runtime.GOMAXPROCS(-1)
N := int32(b.N / batch)
var wg sync.WaitGroup
wg.Add(P)
for p := 0; p < P; p++ {
go func() {
for atomic.AddInt32(&N, -1) >= 0 {
bench(batch)
}
wg.Done()
}()
}
wg.Wait()
この部分では、batch
サイズを1000と定義し、GOMAXPROCS
の数だけgoroutineを起動しています。b.N
をbatch
で割った回数N
をatomic.AddInt32
で管理し、各goroutineがbench(batch)
を繰り返し実行していました。sync.WaitGroup
を使って全てのgoroutineの完了を待機しています。
変更後:
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
bench(1000)
}
})
変更後は、上記の複雑な手動管理のコードが、b.RunParallel
の呼び出し一つに置き換えられています。b.RunParallel
に渡される匿名関数内で、for pb.Next() { ... }
ループを使ってbench(1000)
を実行しています。pb.Next()
は、RunParallel
が内部で管理するイテレーションが残っている限りtrue
を返します。これにより、RunParallel
が自動的にgoroutineの生成、イテレーションの分散、そしてベンチマークのタイミング計測を行います。batch
サイズはbench(1000)
の引数として直接渡されています。
この変更により、ベンチマークコードが大幅に簡素化され、Goのテストフレームワークが提供する並行ベンチマークの機能が活用されるようになりました。
関連リンク
- Go CL 68060043: https://golang.org/cl/68060043
参考にした情報源リンク
testing.B.RunParallel
の解説:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHabOayRShk-4HC-vbUyWlCeBDG5JaAGj0GSYsCIUBndcc0yMasqS-jVghCtNb8MqcUX0ufrA_jGHyywOQ2rXnTumg5tfg6ceNfrUCTtNKbIEa_w4r6J8yXZ8ziA7mgA5mh0tAFOZE5KxgWITEIuYun3CxLH7IiosAHt0-1
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEKO9WepOXWq6PdfXKzS_cdgceAb6VB7noQYEInjw9Uy0ypWFzmqRiP9F8YmB-wx-N2KtDmaQL8RaxP9fL0CDxgHn3p_Ddv0v6kUBGPce8pXVnKwHKj_jp1AonsHxMwzl3fEz2_jEIo2wliPSwcMr7dPUbamDJ2aCttmjNoCpqPm_hd3gc4gsw3MLFmN0o5Kkcopn4RRuhuo4fth9dkXHdJR-A-k-iIehhC8USp
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEQ_Zy2I_A5E-Dur5yeRslatyVq7XejHYZw0fxXFF8-G1ql--kNp_GcIb2UYFoa_ooBKo8z2iPCbKNfFq5WzjfNvOZ6EJFqZK3WN616Gdp8EYr6RNHkxq22lJrmuHvIfTpbPgxDdkqI_8iqMXpRgUWQjhpJ
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHvdyn6WpnqE0l4drq51X4e6hruxsI7vLziTugud8G6iy0M7-i73bZFNOXNRMdi4rU1_HFZt1fQN0S8C4Y3XFbr8XCwEzlYSbe0zhBD-jVh4ln_7JX5
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFlbF4Jq08uo57dnWrVPqdwEFNJ8nNiNThAKejhO04Rgh9zmdKzELgMNQ_B_LGuQfGy4s6LpljraslVoiFl0MMcfzVaJiy9Zbiu4NKzAagKTFi0LY_4UmgPR5trfnFWo7hqEqte2Ib9qh2cw-mB9ohmAc45-Ce0l-o=