[インデックス 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.gosrc/pkg/runtime/mfinal_test.gosrc/pkg/runtime/norace_test.gosrc/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.gosrc/pkg/runtime/mfinal_test.gosrc/pkg/runtime/norace_test.gosrc/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