Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.WaitGroupatomicパッケージを使用して同期を取り、b.N(ベンチマークのイテレーション回数)を各ゴルーチンに分配していました。この手動での並列化は、以下の問題点がありました。

  1. ボイラープレートコードの増加: 各ベンチマークで同様の並列化ロジックを記述する必要があり、コードが冗長になっていました。
  2. 正確性の問題: 手動でのゴルーチン管理やb.Nの分配は、ベンチマークの実行環境(特にGOMAXPROCSの値)に依存し、正確な測定が難しい場合がありました。例えば、GOMAXPROCSが変更された場合に、ベンチマークの並列度が適切に調整されない可能性がありました。
  3. 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は、ベンチマークのセットアップとクリーンアップの時間を測定から除外するために、ResetTimerStopTimerを自動的に呼び出します。
  • runtime.GOMAXPROCS:
    • Goランタイムが同時に実行できるOSスレッドの最大数を設定または取得します。
    • runtime.GOMAXPROCS(n int): nが0より大きい場合、GOMAXPROCSnに設定し、以前の値を返します。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のベンチマークテストにおける並列処理の管理方法の根本的な変更です。

変更前: 変更前のベンチマークコードは、手動で並列処理を実装していました。これは通常、以下のパターンに従っていました。

  1. runtime.GOMAXPROCS(-1)またはruntime.GOMAXPROCS(0)を呼び出して、現在のプロセッサ数を取得します。
  2. そのプロセッサ数に基づいて、procs個のゴルーチンを起動します。
  3. 各ゴルーチンは、b.NCallsPerSched(または同様の定数)で割った値に基づいて、割り当てられたイテレーション数を処理します。
  4. sync/atomicパッケージを使用して、残りのイテレーション数をアトミックに減算し、すべてのゴルーチンが作業を完了するまでループします。
  5. sync.WaitGroupまたはチャネルを使用して、すべてのゴルーチンの完了を待ちます。
  6. runtime.Gosched()を呼び出して、他のゴルーチンにCPUを譲ることで、スケジューリングを助けます。

このアプローチは機能しますが、冗長でエラーが発生しやすく、GOMAXPROCSの変更に対する適応性が低いという欠点がありました。

変更後: 変更後のベンチマークコードは、b.RunParallel関数を使用しています。

  1. b.RunParallel(func(pb *testing.PB) { ... })を呼び出します。
  2. RunParallelは、GOMAXPROCSの値に基づいて、自動的に最適な数のゴルーチンを起動します。
  3. 各ゴルーチンは、pb *testing.PBオブジェクトを受け取る匿名関数を実行します。
  4. 匿名関数内で、for pb.Next() { ... }ループを使用します。pb.Next()は、b.Nのイテレーションを並列に分配し、すべてのイテレーションが完了するまでtrueを返します。これにより、手動でのイテレーション管理やアトミック操作が不要になります。
  5. RunParallelは、ベンチマークのセットアップとクリーンアップの時間を測定から除外するために、内部でb.ResetTimer()b.StopTimer()を適切に呼び出します。

この変更により、ベンチマークコードは大幅に簡素化され、より堅牢で正確な並列ベンチマークが可能になります。特に、GOMAXPROCSの値が実行時に変更されても、RunParallelが自動的に並列度を調整するため、ベンチマークの信頼性が向上します。

コアとなるコードの変更箇所

このコミットでは、以下の4つのテストファイルが変更されています。

  1. src/pkg/runtime/chan_test.go
  2. src/pkg/runtime/mfinal_test.go
  3. src/pkg/runtime/norace_test.go
  4. src/pkg/runtime/proc_test.go

それぞれのファイルで、手動で並列ゴルーチンを起動していたベンチマーク関数が、b.RunParallelを使用するように変更されています。

例1: src/pkg/runtime/chan_test.goBenchmarkChanNonblocking

--- 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.goBenchmarkFinalizer

--- 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)
+		}
+	})
 }

コアとなるコードの解説

上記の例からわかるように、変更の核心は以下の点にあります。

  1. 手動のゴルーチン起動と同期の削除:

    • procs := runtime.GOMAXPROCS(-1)N := int32(b.N / CallsPerSched) といった、プロセッサ数やイテレーション数を手動で計算するロジックが削除されました。
    • for p := 0; p < procs; p++ { go func() { ... } } のようなゴルーチンをループで起動するパターンが削除されました。
    • sync.WaitGroupsync/atomic パッケージを使用した同期メカニズムが削除されました。
    • runtime.Gosched() の呼び出しも不要になりました。
  2. b.RunParallelの導入:

    • すべての並列ベンチマークが b.RunParallel(func(pb *testing.PB) { ... }) の形式に統一されました。
    • RunParallelに渡される匿名関数内で、for pb.Next() { ... } ループが使用されます。このループが、ベンチマーク対象の実際の操作を実行する場所になります。pb.Next() は、b.N のイテレーションを複数のゴルーチンに効率的に分配し、すべてのイテレーションが完了するまでループを継続します。

この変更により、ベンチマークコードは大幅に簡素化され、Goのtestingパッケージが提供する高レベルの並列ベンチマーク機能を利用するようになりました。これにより、ベンチマークの記述が容易になり、ランタイムのスケジューリングやGOMAXPROCSの変更に対する適応性が向上し、より信頼性の高いベンチマーク結果が得られるようになります。

関連リンク

参考にした情報源リンク

  • 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.WaitGroupatomicパッケージを使用して同期を取り、b.N(ベンチマークのイテレーション回数)を各ゴルーチンに分配していました。この手動での並列化は、以下の問題点がありました。

  1. ボイラープレートコードの増加: 各ベンチマークで同様の並列化ロジックを記述する必要があり、コードが冗長になっていました。
  2. 正確性の問題: 手動でのゴルーチン管理やb.Nの分配は、ベンチマークの実行環境(特にGOMAXPROCSの値)に依存し、正確な測定が難しい場合がありました。例えば、GOMAXPROCSが変更された場合に、ベンチマークの並列度が適切に調整されない可能性がありました。
  3. 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は、ベンチマークのセットアップとクリーンアップの時間を測定から除外するために、ResetTimerStopTimerを自動的に呼び出します。
  • runtime.GOMAXPROCS:
    • Goランタイムが同時に実行できるOSスレッドの最大数を設定または取得します。
    • runtime.GOMAXPROCS(n int): nが0より大きい場合、GOMAXPROCSnに設定し、以前の値を返します。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のベンチマークテストにおける並列処理の管理方法の根本的な変更です。

変更前: 変更前のベンチマークコードは、手動で並列処理を実装していました。これは通常、以下のパターンに従っていました。

  1. runtime.GOMAXPROCS(-1)またはruntime.GOMAXPROCS(0)を呼び出して、現在のプロセッサ数を取得します。
  2. そのプロセッサ数に基づいて、procs個のゴルーチンを起動します。
  3. 各ゴルーチンは、b.NCallsPerSched(または同様の定数)で割った値に基づいて、割り当てられたイテレーション数を処理します。
  4. sync/atomicパッケージを使用して、残りのイテレーション数をアトミックに減算し、すべてのゴルーチンが作業を完了するまでループします。
  5. sync.WaitGroupまたはチャネルを使用して、すべてのゴルーチンの完了を待ちます。
  6. runtime.Gosched()を呼び出して、他のゴルーチンにCPUを譲ることで、スケジューリングを助けます。

このアプローチは機能しますが、冗長でエラーが発生しやすく、GOMAXPROCSの変更に対する適応性が低いという欠点がありました。

変更後: 変更後のベンチマークコードは、b.RunParallel関数を使用しています。

  1. b.RunParallel(func(pb *testing.PB) { ... })を呼び出します。
  2. RunParallelは、GOMAXPROCSの値に基づいて、自動的に最適な数のゴルーチンを起動します。
  3. 各ゴルーチンは、pb *testing.PBオブジェクトを受け取る匿名関数を実行します。
  4. 匿名関数内で、for pb.Next() { ... }ループを使用します。pb.Next()は、b.Nのイテレーションを並列に分配し、すべてのイテレーションが完了するまでtrueを返します。これにより、手動でのイテレーション管理やアトミック操作が不要になります。
  5. RunParallelは、ベンチマークのセットアップとクリーンアップの時間を測定から除外するために、内部でb.ResetTimer()b.StopTimer()を適切に呼び出します。

この変更により、ベンチマークコードは大幅に簡素化され、より堅牢で正確な並列ベンチマークが可能になります。特に、GOMAXPROCSの値が実行時に変更されても、RunParallelが自動的に並列度を調整するため、ベンチマークの信頼性が向上します。

コアとなるコードの変更箇所

このコミットでは、以下の4つのテストファイルが変更されています。

  1. src/pkg/runtime/chan_test.go
  2. src/pkg/runtime/mfinal_test.go
  3. src/pkg/runtime/norace_test.go
  4. src/pkg/runtime/proc_test.go

それぞれのファイルで、手動で並列ゴルーチンを起動していたベンチマーク関数が、b.RunParallelを使用するように変更されています。

例1: src/pkg/runtime/chan_test.goBenchmarkChanNonblocking

--- 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.goBenchmarkFinalizer

--- 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)
+		}
+	})
 }

コアとなるコードの解説

上記の例からわかるように、変更の核心は以下の点にあります。

  1. 手動のゴルーチン起動と同期の削除:

    • procs := runtime.GOMAXPROCS(-1)N := int32(b.N / CallsPerSched) といった、プロセッサ数やイテレーション数を手動で計算するロジックが削除されました。
    • for p := 0; p < procs; p++ { go func() { ... } } のようなゴルーチンをループで起動するパターンが削除されました。
    • sync.WaitGroupsync/atomic パッケージを使用した同期メカニズムが削除されました。
    • runtime.Gosched() の呼び出しも不要になりました。
  2. b.RunParallelの導入:

    • すべての並列ベンチマークが b.RunParallel(func(pb *testing.PB) { ... }) の形式に統一されました。
    • RunParallelに渡される匿名関数内で、for pb.Next() { ... } ループが使用されます。このループが、ベンチマーク対象の実際の操作を実行する場所になります。pb.Next() は、b.N のイテレーションを複数のゴルーチンに効率的に分配し、すべてのイテレーションが完了するまでループを継続します。

この変更により、ベンチマークコードは大幅に簡素化され、Goのtestingパッケージが提供する高レベルの並列ベンチマーク機能を利用するようになりました。これにより、ベンチマークの記述が容易になり、ランタイムのスケジューリングやGOMAXPROCSの変更に対する適応性が向上し、より信頼性の高いベンチマーク結果が得られるようになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特にsrc/testing/benchmark.go
  • Go言語のベンチマークに関する一般的な記事やチュートリアル
  • このコミットのGo Gerrit Code Reviewページ: https://golang.org/cl/68020043