[インデックス 18620] ファイルの概要
このコミットは、Go言語のランタイムパッケージにおけるベンチマークテストの改善を目的としています。具体的には、既存のベンチマークコードが手動で並列処理を管理していた部分を、testing
パッケージが提供するRunParallel
ヘルパー関数を使用するように変更しています。これにより、ベンチマークの記述が簡素化され、より正確な並列ベンチマークが可能になります。
コミット
commit 69257d17fe1fbd81e7f720f1a7f4e4f003997ea3
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon Feb 24 20:50:12 2014 +0400
runtime: use RunParallel in more benchmarks
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/68020043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/69257d17fe1fbd81e7f720f1a7f4e4f003997ea3
元コミット内容
runtime: use RunParallel in more benchmarks
このコミットメッセージは、Goランタイムのベンチマークにおいて、testing.B.RunParallel
関数をより多くのベンチマークテストで使用するように変更したことを示しています。
変更の背景
Go言語のベンチマークは、go test -bench=.
コマンドで実行され、testing
パッケージのBenchmark
関数によって定義されます。ベンチマークの目的は、特定の操作のパフォーマンスを測定することです。特に、並列処理のパフォーマンスを測定する場合、複数のゴルーチンを起動して同時に操作を実行させる必要があります。
このコミット以前は、多くの並列ベンチマークが、runtime.GOMAXPROCS
の値に基づいて手動でゴルーチンを起動し、sync.WaitGroup
やatomic
パッケージを使用して同期を取り、b.N
(ベンチマークのイテレーション回数)を各ゴルーチンに分配していました。この手動での並列化は、以下の問題点がありました。
- ボイラープレートコードの増加: 各ベンチマークで同様の並列化ロジックを記述する必要があり、コードが冗長になっていました。
- 正確性の問題: 手動でのゴルーチン管理や
b.N
の分配は、ベンチマークの実行環境(特にGOMAXPROCS
の値)に依存し、正確な測定が難しい場合がありました。例えば、GOMAXPROCS
が変更された場合に、ベンチマークの並列度が適切に調整されない可能性がありました。 testing
パッケージの進化:testing
パッケージは、Go 1.1でRunParallel
関数を導入しました。この関数は、GOMAXPROCS
の値に基づいて自動的に並列ゴルーチンを起動し、b.N
を効率的に分配することで、並列ベンチマークをより簡単かつ正確に記述できるように設計されています。
このコミットは、これらの問題を解決し、ベンチマークコードの可読性、保守性、および正確性を向上させるために、手動の並列化ロジックをRunParallel
に置き換えることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とtesting
パッケージの機能に関する知識が必要です。
testing
パッケージとベンチマーク:- Go言語の標準ライブラリの一部で、テストとベンチマーク機能を提供します。
- ベンチマーク関数は
func BenchmarkXxx(b *testing.B)
というシグネチャを持ちます。 b.N
は、ベンチマーク対象の操作が実行されるイテレーション回数を示します。go test
コマンドが自動的に調整し、統計的に有意な結果が得られるようにします。b.ResetTimer()
: ベンチマーク対象のコードの実行時間を測定するタイマーをリセットします。b.StopTimer()
: タイマーを停止します。b.StartTimer()
: タイマーを開始します。b.SetParallelism(p int)
:RunParallel
が使用する並列度を設定します。デフォルトはGOMAXPROCS
です。
testing.B.RunParallel(body func(pb *testing.PB))
:- Go 1.1で導入されたベンチマークヘルパー関数です。
GOMAXPROCS
の値に基づいて、複数のゴルーチンを自動的に起動します。- 各ゴルーチンは、
pb *testing.PB
型の引数を受け取るbody
関数を実行します。 pb.Next()
:body
関数内でループとして使用され、b.N
のイテレーションを並列に分配します。pb.Next()
がtrue
を返す限り、操作を続行します。RunParallel
は、ベンチマークのセットアップとクリーンアップの時間を測定から除外するために、ResetTimer
とStopTimer
を自動的に呼び出します。
runtime.GOMAXPROCS
:- Goランタイムが同時に実行できるOSスレッドの最大数を設定または取得します。
runtime.GOMAXPROCS(n int)
:n
が0より大きい場合、GOMAXPROCS
をn
に設定し、以前の値を返します。n
が0の場合、現在のGOMAXPROCS
の値を返します。
sync/atomic
パッケージ:- 低レベルのアトミック操作(不可分操作)を提供します。複数のゴルーチンから共有変数に安全にアクセスするために使用されます。
atomic.AddInt32(&N, -1)
:N
から1をアトミックに減算し、新しい値を返します。
sync.WaitGroup
:- 複数のゴルーチンの完了を待つために使用されます。
wg.Add(delta int)
: カウンタにdelta
を加算します。wg.Done()
: カウンタを1減算します。wg.Wait()
: カウンタが0になるまでブロックします。
runtime.Gosched()
:- 現在のゴルーチンを一時停止し、他のゴルーチンが実行されるようにCPUを譲ります。
runtime.Entersyscall()
/runtime.Exitsyscall()
:- Goランタイムに、ゴルーチンがシステムコールに入ろうとしている(またはシステムコールから戻った)ことを通知します。これにより、ランタイムはスケジューリングを適切に調整できます。
runtime.SetFinalizer(obj interface{}, finalizer interface{})
:- オブジェクト
obj
がガベージコレクションされる直前に実行されるファイナライザ関数finalizer
を設定します。
- オブジェクト
技術的詳細
このコミットの主要な技術的変更は、Goのベンチマークテストにおける並列処理の管理方法の根本的な変更です。
変更前: 変更前のベンチマークコードは、手動で並列処理を実装していました。これは通常、以下のパターンに従っていました。
runtime.GOMAXPROCS(-1)
またはruntime.GOMAXPROCS(0)
を呼び出して、現在のプロセッサ数を取得します。- そのプロセッサ数に基づいて、
procs
個のゴルーチンを起動します。 - 各ゴルーチンは、
b.N
をCallsPerSched
(または同様の定数)で割った値に基づいて、割り当てられたイテレーション数を処理します。 sync/atomic
パッケージを使用して、残りのイテレーション数をアトミックに減算し、すべてのゴルーチンが作業を完了するまでループします。sync.WaitGroup
またはチャネルを使用して、すべてのゴルーチンの完了を待ちます。runtime.Gosched()
を呼び出して、他のゴルーチンにCPUを譲ることで、スケジューリングを助けます。
このアプローチは機能しますが、冗長でエラーが発生しやすく、GOMAXPROCS
の変更に対する適応性が低いという欠点がありました。
変更後:
変更後のベンチマークコードは、b.RunParallel
関数を使用しています。
b.RunParallel(func(pb *testing.PB) { ... })
を呼び出します。RunParallel
は、GOMAXPROCS
の値に基づいて、自動的に最適な数のゴルーチンを起動します。- 各ゴルーチンは、
pb *testing.PB
オブジェクトを受け取る匿名関数を実行します。 - 匿名関数内で、
for pb.Next() { ... }
ループを使用します。pb.Next()
は、b.N
のイテレーションを並列に分配し、すべてのイテレーションが完了するまでtrue
を返します。これにより、手動でのイテレーション管理やアトミック操作が不要になります。 RunParallel
は、ベンチマークのセットアップとクリーンアップの時間を測定から除外するために、内部でb.ResetTimer()
とb.StopTimer()
を適切に呼び出します。
この変更により、ベンチマークコードは大幅に簡素化され、より堅牢で正確な並列ベンチマークが可能になります。特に、GOMAXPROCS
の値が実行時に変更されても、RunParallel
が自動的に並列度を調整するため、ベンチマークの信頼性が向上します。
コアとなるコードの変更箇所
このコミットでは、以下の4つのテストファイルが変更されています。
src/pkg/runtime/chan_test.go
src/pkg/runtime/mfinal_test.go
src/pkg/runtime/norace_test.go
src/pkg/runtime/proc_test.go
それぞれのファイルで、手動で並列ゴルーチンを起動していたベンチマーク関数が、b.RunParallel
を使用するように変更されています。
例1: src/pkg/runtime/chan_test.go
の BenchmarkChanNonblocking
--- a/src/pkg/runtime/chan_test.go
+++ b/src/pkg/runtime/chan_test.go
@@ -431,27 +431,15 @@ func TestMultiConsumer(t *testing.T) {
}
func BenchmarkChanNonblocking(b *testing.B) {
- const CallsPerSched = 1000
- procs := runtime.GOMAXPROCS(-1)
- N := int32(b.N / CallsPerSched)
- c := make(chan bool, procs)
myc := make(chan int)
- for p := 0; p < procs; p++ {
- go func() {
- for atomic.AddInt32(&N, -1) >= 0 {
- for g := 0; g < CallsPerSched; g++ {
- select {
- case <-myc:
- default:
- }
- }
- }
- c <- true
- }()
- }
- for p := 0; p < procs; p++ {
- <-c
- }
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ select {
+ case <-myc:
+ default:
+ }
+ }
+ })
}
func BenchmarkSelectUncontended(b *testing.B) {
例2: src/pkg/runtime/mfinal_test.go
の BenchmarkFinalizer
--- a/src/pkg/runtime/mfinal_test.go
+++ b/src/pkg/runtime/mfinal_test.go
@@ -6,8 +6,6 @@ package runtime_test
import (
"runtime"
- "sync"
- "sync/atomic"
"testing"
"time"
)
@@ -112,50 +110,28 @@ func TestFinalizerZeroSizedStruct(t *testing.T) {
}
func BenchmarkFinalizer(b *testing.B) {
- const CallsPerSched = 1000
- procs := runtime.GOMAXPROCS(-1)
- N := int32(b.N / CallsPerSched)
- var wg sync.WaitGroup
- wg.Add(procs)
- for p := 0; p < procs; p++ {
- go func() {
- var data [CallsPerSched]*int
- for i := 0; i < CallsPerSched; i++ {
- data[i] = new(int)
+ const Batch = 1000
+ b.RunParallel(func(pb *testing.PB) {
+ var data [Batch]*int
+ for i := 0; i < Batch; i++ {
+ data[i] = new(int)
+ }
+ for pb.Next() {
+ for i := 0; i < Batch; i++ {
+ runtime.SetFinalizer(data[i], fin)
}
- for atomic.AddInt32(&N, -1) >= 0 {
- runtime.Gosched()
- for i := 0; i < CallsPerSched; i++ {
- runtime.SetFinalizer(data[i], fin)
- }
- for i := 0; i < CallsPerSched; i++ {
- runtime.SetFinalizer(data[i], nil)
- }
+ for i := 0; i < Batch; i++ {
+ runtime.SetFinalizer(data[i], nil)
}
- wg.Done()
- }()
- }
- wg.Wait()
+ }
+ })
}
func BenchmarkFinalizerRun(b *testing.B) {
- const CallsPerSched = 1000
- procs := runtime.GOMAXPROCS(-1)
- N := int32(b.N / CallsPerSched)
- var wg sync.WaitGroup
- wg.Add(procs)
- for p := 0; p < procs; p++ {
- go func() {
- for atomic.AddInt32(&N, -1) >= 0 {
- runtime.Gosched()
- for i := 0; i < CallsPerSched; i++ {
- v := new(int)
- runtime.SetFinalizer(v, fin)
- }
- runtime.GC()
- }
- wg.Done()
- }()
- }
- wg.Wait()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ v := new(int)
+ runtime.SetFinalizer(v, fin)
+ }
+ })
}
コアとなるコードの解説
上記の例からわかるように、変更の核心は以下の点にあります。
-
手動のゴルーチン起動と同期の削除:
procs := runtime.GOMAXPROCS(-1)
やN := int32(b.N / CallsPerSched)
といった、プロセッサ数やイテレーション数を手動で計算するロジックが削除されました。for p := 0; p < procs; p++ { go func() { ... } }
のようなゴルーチンをループで起動するパターンが削除されました。sync.WaitGroup
やsync/atomic
パッケージを使用した同期メカニズムが削除されました。runtime.Gosched()
の呼び出しも不要になりました。
-
b.RunParallel
の導入:- すべての並列ベンチマークが
b.RunParallel(func(pb *testing.PB) { ... })
の形式に統一されました。 RunParallel
に渡される匿名関数内で、for pb.Next() { ... }
ループが使用されます。このループが、ベンチマーク対象の実際の操作を実行する場所になります。pb.Next()
は、b.N
のイテレーションを複数のゴルーチンに効率的に分配し、すべてのイテレーションが完了するまでループを継続します。
- すべての並列ベンチマークが
この変更により、ベンチマークコードは大幅に簡素化され、Goのtesting
パッケージが提供する高レベルの並列ベンチマーク機能を利用するようになりました。これにより、ベンチマークの記述が容易になり、ランタイムのスケジューリングやGOMAXPROCS
の変更に対する適応性が向上し、より信頼性の高いベンチマーク結果が得られるようになります。
関連リンク
- Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing testing.B.RunParallel
のドキュメント: https://pkg.go.dev/testing#B.RunParallel- Go 1.1 Release Notes (RunParallelの導入について): https://go.dev/doc/go1.1#testing
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/testing/benchmark.go
) - Go言語のベンチマークに関する一般的な記事やチュートリアル
- このコミットのGo Gerrit Code Reviewページ: https://golang.org/cl/68020043
[インデックス 18620] ファイルの概要
このコミットは、Go言語のランタイムパッケージにおけるベンチマークテストの改善を目的としています。具体的には、既存のベンチマークコードが手動で並列処理を管理していた部分を、testing
パッケージが提供するRunParallel
ヘルパー関数を使用するように変更しています。これにより、ベンチマークの記述が簡素化され、より正確な並列ベンチマークが可能になります。
コミット
commit 69257d17fe1fbd81e7f720f1a7f4e4f003997ea3
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon Feb 24 20:50:12 2014 +0400
runtime: use RunParallel in more benchmarks
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/68020043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/69257d17fe1fbd81e7f720f1a7f4e4f003997ea3
元コミット内容
runtime: use RunParallel in more benchmarks
このコミットメッセージは、Goランタイムのベンチマークにおいて、testing.B.RunParallel
関数をより多くのベンチマークテストで使用するように変更したことを示しています。
変更の背景
Go言語のベンチマークは、go test -bench=.
コマンドで実行され、testing
パッケージのBenchmark
関数によって定義されます。ベンチマークの目的は、特定の操作のパフォーマンスを測定することです。特に、並列処理のパフォーマンスを測定する場合、複数のゴルーチンを起動して同時に操作を実行させる必要があります。
このコミット以前は、多くの並列ベンチマークが、runtime.GOMAXPROCS
の値に基づいて手動でゴルーチンを起動し、sync.WaitGroup
やatomic
パッケージを使用して同期を取り、b.N
(ベンチマークのイテレーション回数)を各ゴルーチンに分配していました。この手動での並列化は、以下の問題点がありました。
- ボイラープレートコードの増加: 各ベンチマークで同様の並列化ロジックを記述する必要があり、コードが冗長になっていました。
- 正確性の問題: 手動でのゴルーチン管理や
b.N
の分配は、ベンチマークの実行環境(特にGOMAXPROCS
の値)に依存し、正確な測定が難しい場合がありました。例えば、GOMAXPROCS
が変更された場合に、ベンチマークの並列度が適切に調整されない可能性がありました。 testing
パッケージの進化:testing
パッケージは、Go 1.1でRunParallel
関数を導入しました。この関数は、GOMAXPROCS
の値に基づいて自動的に並列ゴルーチンを起動し、b.N
を効率的に分配することで、並列ベンチマークをより簡単かつ正確に記述できるように設計されています。
このコミットは、これらの問題を解決し、ベンチマークコードの可読性、保守性、および正確性を向上させるために、手動の並列化ロジックをRunParallel
に置き換えることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とtesting
パッケージの機能に関する知識が必要です。
testing
パッケージとベンチマーク:- Go言語の標準ライブラリの一部で、テストとベンチマーク機能を提供します。
- ベンチマーク関数は
func BenchmarkXxx(b *testing.B)
というシグネチャを持ちます。 b.N
は、ベンチマーク対象の操作が実行されるイテレーション回数を示します。go test
コマンドが自動的に調整し、統計的に有意な結果が得られるようにします。b.ResetTimer()
: ベンチマーク対象のコードの実行時間を測定するタイマーをリセットします。b.StopTimer()
: タイマーを停止します。b.StartTimer()
: タイマーを開始します。b.SetParallelism(p int)
:RunParallel
が使用する並列度を設定します。デフォルトはGOMAXPROCS
です。
testing.B.RunParallel(body func(pb *testing.PB))
:- Go 1.1で導入されたベンチマークヘルパー関数です。
GOMAXPROCS
の値に基づいて、複数のゴルーチンを自動的に起動します。- 各ゴルーチンは、
pb *testing.PB
型の引数を受け取るbody
関数を実行します。 pb.Next()
:body
関数内でループとして使用され、b.N
のイテレーションを並列に分配します。pb.Next()
がtrue
を返す限り、操作を続行します。RunParallel
は、ベンチマークのセットアップとクリーンアップの時間を測定から除外するために、ResetTimer
とStopTimer
を自動的に呼び出します。
runtime.GOMAXPROCS
:- Goランタイムが同時に実行できるOSスレッドの最大数を設定または取得します。
runtime.GOMAXPROCS(n int)
:n
が0より大きい場合、GOMAXPROCS
をn
に設定し、以前の値を返します。n
が0の場合、現在のGOMAXPROCS
の値を返します。
sync/atomic
パッケージ:- 低レベルのアトミック操作(不可分操作)を提供します。複数のゴルーチンから共有変数に安全にアクセスするために使用されます。
atomic.AddInt32(&N, -1)
:N
から1をアトミックに減算し、新しい値を返します。
sync.WaitGroup
:- 複数のゴルーチンの完了を待つために使用されます。
wg.Add(delta int)
: カウンタにdelta
を加算します。wg.Done()
: カウンタを1減算します。wg.Wait()
: カウンタが0になるまでブロックします。
runtime.Gosched()
:- 現在のゴルーチンを一時停止し、他のゴルーチンが実行されるようにCPUを譲ります。
runtime.Entersyscall()
/runtime.Exitsyscall()
:- Goランタイムに、ゴルーチンがシステムコールに入ろうとしている(またはシステムコールから戻った)ことを通知します。これにより、ランタイムはスケジューリングを適切に調整できます。
runtime.SetFinalizer(obj interface{}, finalizer interface{})
:- オブジェクト
obj
がガベージコレクションされる直前に実行されるファイナライザ関数finalizer
を設定します。
- オブジェクト
技術的詳細
このコミットの主要な技術的変更は、Goのベンチマークテストにおける並列処理の管理方法の根本的な変更です。
変更前: 変更前のベンチマークコードは、手動で並列処理を実装していました。これは通常、以下のパターンに従っていました。
runtime.GOMAXPROCS(-1)
またはruntime.GOMAXPROCS(0)
を呼び出して、現在のプロセッサ数を取得します。- そのプロセッサ数に基づいて、
procs
個のゴルーチンを起動します。 - 各ゴルーチンは、
b.N
をCallsPerSched
(または同様の定数)で割った値に基づいて、割り当てられたイテレーション数を処理します。 sync/atomic
パッケージを使用して、残りのイテレーション数をアトミックに減算し、すべてのゴルーチンが作業を完了するまでループします。sync.WaitGroup
またはチャネルを使用して、すべてのゴルーチンの完了を待ちます。runtime.Gosched()
を呼び出して、他のゴルーチンにCPUを譲ることで、スケジューリングを助けます。
このアプローチは機能しますが、冗長でエラーが発生しやすく、GOMAXPROCS
の変更に対する適応性が低いという欠点がありました。
変更後:
変更後のベンチマークコードは、b.RunParallel
関数を使用しています。
b.RunParallel(func(pb *testing.PB) { ... })
を呼び出します。RunParallel
は、GOMAXPROCS
の値に基づいて、自動的に最適な数のゴルーチンを起動します。- 各ゴルーチンは、
pb *testing.PB
オブジェクトを受け取る匿名関数を実行します。 - 匿名関数内で、
for pb.Next() { ... }
ループを使用します。pb.Next()
は、b.N
のイテレーションを並列に分配し、すべてのイテレーションが完了するまでtrue
を返します。これにより、手動でのイテレーション管理やアトミック操作が不要になります。 RunParallel
は、ベンチマークのセットアップとクリーンアップの時間を測定から除外するために、内部でb.ResetTimer()
とb.StopTimer()
を適切に呼び出します。
この変更により、ベンチマークコードは大幅に簡素化され、より堅牢で正確な並列ベンチマークが可能になります。特に、GOMAXPROCS
の値が実行時に変更されても、RunParallel
が自動的に並列度を調整するため、ベンチマークの信頼性が向上します。
コアとなるコードの変更箇所
このコミットでは、以下の4つのテストファイルが変更されています。
src/pkg/runtime/chan_test.go
src/pkg/runtime/mfinal_test.go
src/pkg/runtime/norace_test.go
src/pkg/runtime/proc_test.go
それぞれのファイルで、手動で並列ゴルーチンを起動していたベンチマーク関数が、b.RunParallel
を使用するように変更されています。
例1: src/pkg/runtime/chan_test.go
の BenchmarkChanNonblocking
--- a/src/pkg/runtime/chan_test.go
+++ b/src/pkg/runtime/chan_test.go
@@ -431,27 +431,15 @@ func TestMultiConsumer(t *testing.T) {
}
func BenchmarkChanNonblocking(b *testing.B) {
- const CallsPerSched = 1000
- procs := runtime.GOMAXPROCS(-1)
- N := int32(b.N / CallsPerSched)
- c := make(chan bool, procs)
myc := make(chan int)
- for p := 0; p < procs; p++ {
- go func() {
- for atomic.AddInt32(&N, -1) >= 0 {
- for g := 0; g < CallsPerSched; g++ {
- select {
- case <-myc:
- default:
- }
- }
- }
- c <- true
- }()
- }
- for p := 0; p < procs; p++ {
- <-c
- }
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ select {
+ case <-myc:
+ default:
+ }
+ }
+ })
}
func BenchmarkSelectUncontended(b *testing.B) {
例2: src/pkg/runtime/mfinal_test.go
の BenchmarkFinalizer
--- a/src/pkg/runtime/mfinal_test.go
+++ b/src/pkg/runtime/mfinal_test.go
@@ -6,8 +6,6 @@ package runtime_test
import (
"runtime"
- "sync"
- "sync/atomic"
"testing"
"time"
)
@@ -112,50 +110,28 @@ func TestFinalizerZeroSizedStruct(t *testing.T) {
}
func BenchmarkFinalizer(b *testing.B) {
- const CallsPerSched = 1000
- procs := runtime.GOMAXPROCS(-1)
- N := int32(b.N / CallsPerSched)
- var wg sync.WaitGroup
- wg.Add(procs)
- for p := 0; p < procs; p++ {
- go func() {
- var data [CallsPerSched]*int
- for i := 0; i < CallsPerSched; i++ {
- data[i] = new(int)
+ const Batch = 1000
+ b.RunParallel(func(pb *testing.PB) {
+ var data [Batch]*int
+ for i := 0; i < Batch; i++ {
+ data[i] = new(int)
+ }
+ for pb.Next() {
+ for i := 0; i < Batch; i++ {
+ runtime.SetFinalizer(data[i], fin)
}
- for atomic.AddInt32(&N, -1) >= 0 {
- runtime.Gosched()
- for i := 0; i < CallsPerSched; i++ {
- runtime.SetFinalizer(data[i], fin)
- }
- for i := 0; i < CallsPerSched; i++ {
- runtime.SetFinalizer(data[i], nil)
- }
+ for i := 0; i < Batch; i++ {
+ runtime.SetFinalizer(data[i], nil)
}
- wg.Done()
- }()
- }
- wg.Wait()
+ }
+ })
}
func BenchmarkFinalizerRun(b *testing.B) {
- const CallsPerSched = 1000
- procs := runtime.GOMAXPROCS(-1)
- N := int32(b.N / CallsPerSched)
- var wg sync.WaitGroup
- wg.Add(procs)
- for p := 0; p < procs; p++ {
- go func() {
- for atomic.AddInt32(&N, -1) >= 0 {
- runtime.Gosched()
- for i := 0; i < CallsPerSched; i++ {
- v := new(int)
- runtime.SetFinalizer(v, fin)
- }
- runtime.GC()
- }
- wg.Done()
- }()
- }
- wg.Wait()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ v := new(int)
+ runtime.SetFinalizer(v, fin)
+ }
+ })
}
コアとなるコードの解説
上記の例からわかるように、変更の核心は以下の点にあります。
-
手動のゴルーチン起動と同期の削除:
procs := runtime.GOMAXPROCS(-1)
やN := int32(b.N / CallsPerSched)
といった、プロセッサ数やイテレーション数を手動で計算するロジックが削除されました。for p := 0; p < procs; p++ { go func() { ... } }
のようなゴルーチンをループで起動するパターンが削除されました。sync.WaitGroup
やsync/atomic
パッケージを使用した同期メカニズムが削除されました。runtime.Gosched()
の呼び出しも不要になりました。
-
b.RunParallel
の導入:- すべての並列ベンチマークが
b.RunParallel(func(pb *testing.PB) { ... })
の形式に統一されました。 RunParallel
に渡される匿名関数内で、for pb.Next() { ... }
ループが使用されます。このループが、ベンチマーク対象の実際の操作を実行する場所になります。pb.Next()
は、b.N
のイテレーションを複数のゴルーチンに効率的に分配し、すべてのイテレーションが完了するまでループを継続します。
- すべての並列ベンチマークが
この変更により、ベンチマークコードは大幅に簡素化され、Goのtesting
パッケージが提供する高レベルの並列ベンチマーク機能を利用するようになりました。これにより、ベンチマークの記述が容易になり、ランタイムのスケジューリングやGOMAXPROCS
の変更に対する適応性が向上し、より信頼性の高いベンチマーク結果が得られるようになります。
関連リンク
- Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing testing.B.RunParallel
のドキュメント: https://pkg.go.dev/testing#B.RunParallel- Go 1.1 Release Notes (RunParallelの導入について): https://go.dev/doc/go1.1#testing
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/testing/benchmark.go
) - Go言語のベンチマークに関する一般的な記事やチュートリアル
- このコミットのGo Gerrit Code Reviewページ: https://golang.org/cl/68020043