[インデックス 12842] ファイルの概要
このコミットは、Go言語のベンチマークスイート内のガベージコレクション関連のベンチマーク、特にtest/bench/garbage
ディレクトリにあるベンチマークの修正と改善を目的としています。主な変更点は、パーサーベンチマークの修正、tree2
ベンチマークへの標準ベンチマーク出力の追加、そしてgo test
コマンドと同様にGOMAXPROCS
の値をベンチマーク出力に含めるようにしたことです。これにより、ベンチマーク結果の整合性と比較可能性が向上します。
コミット
commit 77e1227a021c1a7a651fe5fd4965a800d48f8c1b
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Apr 5 20:35:54 2012 +0400
test/bench/garbage: fix parser benchmark
+add standard bench output to tree2
+print GOMAXPROCS as go test does
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5992044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/77e1227a021c1a7a651fe5fd4965a800d48f8c1b
元コミット内容
test/bench/garbage: fix parser benchmark
+add standard bench output to tree2
+print GOMAXPROCS as go test does
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5992044
変更の背景
このコミットは、Go言語のベンチマークシステムにおけるいくつかの課題に対処するために行われました。
- パーサーベンチマークの修正:
test/bench/garbage/parser.go
内のベンチマークが正しく機能していなかったか、または不要な依存関係を含んでいた可能性があります。特に、exp/signal
とtesting/script
パッケージの削除は、これらのパッケージがベンチマークの目的から外れていたか、あるいはGoの標準ライブラリの進化に伴い非推奨になったことを示唆しています。 tree2
ベンチマークへの標準出力の追加:tree2.go
は、ガベージコレクションのパフォーマンスを測定するためのベンチマークの一つですが、これまでの実装ではgo test -bench
コマンドが期待する標準的なベンチマーク出力を生成していませんでした。この変更により、tree2
ベンチマークの結果も他のベンチマークと同様に自動的に解析・集計できるようになり、Goのパフォーマンスダッシュボードなどで一元的に管理できるようになります。GOMAXPROCS
の出力への追加:go test
コマンドでベンチマークを実行する際、通常はGOMAXPROCS
(Goランタイムが使用するOSスレッドの最大数)の値がベンチマーク名に付加されます(例:BenchmarkFoo-4
はGOMAXPROCS=4
で実行されたことを示す)。これは、並列処理の度合いがベンチマーク結果に大きく影響するため、結果の比較において非常に重要な情報です。このコミット以前は、test/bench/garbage
内のカスタムベンチマーク出力にはこの情報が含まれていませんでした。GOMAXPROCS
を明示的に出力に含めることで、ベンチマーク結果の再現性と比較可能性が向上し、異なる環境や設定でのパフォーマンス特性をより正確に評価できるようになります。
これらの変更は、Go言語のパフォーマンス測定の信頼性と利便性を高めるための継続的な取り組みの一環です。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とベンチマークに関する知識が役立ちます。
- Go言語のベンチマーク: Goには、
testing
パッケージを通じて組み込みのベンチマーク機能が提供されています。go test -bench
コマンドを使用すると、関数のパフォーマンスを測定し、実行時間やメモリ割り当てなどの統計情報を取得できます。ベンチマーク関数はBenchmarkXxx
という命名規則に従い、*testing.B
型の引数を取ります。 GOMAXPROCS
:GOMAXPROCS
は、Goランタイムが同時に実行できるOSスレッドの最大数を制御する環境変数、またはruntime.GOMAXPROCS
関数で設定できる値です。この値は、Goプログラムの並列実行性能に直接影響を与えます。ベンチマーク結果はGOMAXPROCS
の値によって大きく変動するため、ベンチマーク結果を比較する際にはこの値を考慮することが不可欠です。runtime.MemStats
とruntime.ReadMemStats
:runtime
パッケージは、Goランタイムに関する低レベルの機能を提供します。runtime.MemStats
構造体は、Goプログラムのメモリ割り当て、ガベージコレクションの統計、ヒープの使用状況など、詳細なメモリ統計情報を含んでいます。runtime.ReadMemStats
関数は、現在のメモリ統計情報をruntime.MemStats
構造体に読み込みます。これらの情報は、ガベージコレクションのパフォーマンスを分析する上で非常に重要です。- ガベージコレクション (GC): Goは自動メモリ管理(ガベージコレクション)を採用しています。GCは、不要になったメモリを自動的に解放し、プログラマが手動でメモリを管理する手間を省きます。しかし、GCの実行はプログラムの実行を一時停止させる(ストップ・ザ・ワールド)ことがあり、これがパフォーマンスに影響を与える可能性があります。そのため、GCのパフォーマンスを測定し、最適化することは、Goアプリケーションの全体的な性能にとって重要です。
- 標準ベンチマーク出力:
go test -bench
が生成する標準的なベンチマーク出力は、特定のフォーマットに従っています。例えば、BenchmarkName-GOMAXPROCS N iter X ns/op
のような形式です。このフォーマットは、自動化されたツールやダッシュボードがベンチマーク結果を解析し、経時的なパフォーマンスの変化を追跡するために使用されます。
技術的詳細
このコミットは、主に3つのファイルにわたる変更を含んでいます。
-
test/bench/garbage/parser.go
の変更:packages
配列から"exp/signal"
と"testing/script"
が削除されました。exp/signal
は、Goの初期段階で実験的なシグナル処理を提供していたパッケージですが、後にos/signal
に統合されたか、その機能が標準ライブラリの他の部分で提供されるようになった可能性があります。testing/script
は、Goのテストフレームワークの一部としてスクリプトベースのテストをサポートしていた可能性がありますが、このベンチマークの目的には不要になったか、あるいは非推奨になったと考えられます。- これらの削除は、ベンチマークの依存関係を整理し、不要なオーバーヘッドを削減することを目的としています。
-
test/bench/garbage/stats.go
の変更:gcstats
関数内で、GOMAXPROCS
の値をベンチマーク出力に含めるためのロジックが追加されました。runtime.GOMAXPROCS(-1)
を呼び出すことで、現在のGOMAXPROCS
の値を取得します。nprocs
が1でない場合、cpus
変数に"-GOMAXPROCS値"
(例:"-4"
)という文字列が設定されます。- この
cpus
変数が、fmt.Printf
で出力されるベンチマーク名の部分に挿入されるようになりました。これにより、go test
コマンドの標準出力と同様に、GOMAXPROCS
の値がベンチマーク結果に明示的に表示されるようになります。 - この変更は、ガベージコレクションの統計情報(メモリ割り当て、ヒープ使用量、GC一時停止時間など)を出力する際に、その統計がどの
GOMAXPROCS
設定下で取得されたかを明確にするために重要です。
-
test/bench/garbage/tree2.go
の変更:time
パッケージがインポートされました。これは、ベンチマークの実行時間を測定するために必要です。main
関数内で、ベンチマークの実行時間を測定するためのtime.Now()
とtime.Sub()
が使用されるようになりました。gcstats("BenchmarkTree2", N, time.Now().Sub(t0))
という行が追加され、tree2
ベンチマークの結果がgcstats
関数を通じて標準ベンチマーク出力形式で表示されるようになりました。N
はベンチマークのイテレーション回数(ここでは10
)を表し、time.Now().Sub(t0)
はベンチマークの総実行時間を表します。- この変更により、
tree2
ベンチマークの結果も、他のGoベンチマークと同様に、自動解析ツールによって処理できるようになります。
これらの変更は全体として、Goのベンチマークインフラストラクチャの堅牢性と使いやすさを向上させることを目的としています。
コアとなるコードの変更箇所
test/bench/garbage/parser.go
--- a/test/bench/garbage/parser.go
+++ b/test/bench/garbage/parser.go
@@ -195,7 +195,6 @@ var packages = []string{
"mime",
"net",
"os",
-\t"exp/signal",
"path",
"math/rand",
"reflect",
@@ -215,7 +214,6 @@ var packages = []string{
"testing",
"testing/iotest",
"testing/quick",
-\t"testing/script",
"time",
"unicode",
"unicode/utf8",
test/bench/garbage/stats.go
--- a/test/bench/garbage/stats.go
+++ b/test/bench/garbage/stats.go
@@ -14,16 +14,21 @@ import (
func gcstats(name string, n int, t time.Duration) {
st := new(runtime.MemStats)
runtime.ReadMemStats(st)
-\tfmt.Printf("garbage.%sMem Alloc=%d/%d Heap=%d NextGC=%d Mallocs=%d\n", name, st.Alloc, st.TotalAlloc, st.Sys, st.NextGC, st.Mallocs)
-\tfmt.Printf("garbage.%s %d %d ns/op\n", name, n, t.Nanoseconds()/int64(n))
-\tfmt.Printf("garbage.%sLastPause 1 %d ns/op\n", name, st.PauseNs[(st.NumGC-1)%uint32(len(st.PauseNs))])
-\tfmt.Printf("garbage.%sPause %d %d ns/op\n", name, st.NumGC, int64(st.PauseTotalNs)/int64(st.NumGC))
+\tnprocs := runtime.GOMAXPROCS(-1)
+\tcpus := ""
+\tif nprocs != 1 {
+\t\tcpus = fmt.Sprintf("-%d", nprocs)
+\t}\n+\tfmt.Printf("garbage.%sMem%s Alloc=%d/%d Heap=%d NextGC=%d Mallocs=%d\n", name, cpus, st.Alloc, st.TotalAlloc, st.Sys, st.NextGC, st.Mallocs)
+\tfmt.Printf("garbage.%s%s %d %d ns/op\n", name, cpus, n, t.Nanoseconds()/int64(n))
+\tfmt.Printf("garbage.%sLastPause%s 1 %d ns/op\n", name, cpus, st.PauseNs[(st.NumGC-1)%uint32(len(st.PauseNs))])
+\tfmt.Printf("garbage.%sPause%s %d %d ns/op\n", name, cpus, st.NumGC, int64(st.PauseTotalNs)/int64(st.NumGC))
\tnn := int(st.NumGC)
\tif nn >= len(st.PauseNs) {\n \t\tnn = len(st.PauseNs)\n \t}\n \tt1, t2, t3, t4, t5 := tukey5(st.PauseNs[0:nn])
-\tfmt.Printf("garbage.%sPause5: %d %d %d %d %d\n", name, t1, t2, t3, t4, t5)
+\tfmt.Printf("garbage.%sPause5%s: %d %d %d %d %d\n", name, cpus, t1, t2, t3, t4, t5)
\n \t//\tfmt.Printf("garbage.%sScan: %v\\n", name, st.ScanDist)\n }\n```
### `test/bench/garbage/tree2.go`
```diff
--- a/test/bench/garbage/tree2.go
+++ b/test/bench/garbage/tree2.go
@@ -11,6 +11,7 @@ import (
"os"
"runtime"
"runtime/pprof"
+\t"time"
"unsafe"
)
@@ -83,7 +84,12 @@ func main() {
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}\n-\tfor i := 0; i < 10; i++ {\n+\tconst N = 10
+\tvar t0 time.Time
+\tfor i := 0; i < N; i++ {\n+\t\tt0 = time.Now()\n \t\tgc()\n \t}\n+\t// Standard gotest benchmark output, collected by build dashboard.\n+\tgcstats("BenchmarkTree2", N, time.Now().Sub(t0))\n }\n```
## コアとなるコードの解説
### `test/bench/garbage/parser.go`の変更点
* **`- "exp/signal"`**: `exp/signal`パッケージのインポートが削除されました。このパッケージはGoの初期段階で実験的なシグナル処理を提供していましたが、後に`os/signal`パッケージに統合されたか、その機能が標準ライブラリの他の部分で提供されるようになったため、このベンチマークでは不要になりました。
* **`- "testing/script"`**: `testing/script`パッケージのインポートが削除されました。このパッケージは、Goのテストフレームワークの一部としてスクリプトベースのテストをサポートしていた可能性がありますが、`parser`ベンチマークの目的には直接関係がなくなったか、非推奨になったと考えられます。
これらの削除は、ベンチマークの依存関係をクリーンアップし、不要なコードを排除することを目的としています。
### `test/bench/garbage/stats.go`の変更点
このファイルでは、`gcstats`関数が変更され、ベンチマーク出力に`GOMAXPROCS`の値を含めるようになりました。
* **`nprocs := runtime.GOMAXPROCS(-1)`**: `runtime.GOMAXPROCS(-1)`を呼び出すことで、現在の`GOMAXPROCS`の値を副作用なく取得します。
* **`cpus := ""` と `if nprocs != 1 { cpus = fmt.Sprintf("-%d", nprocs) }`**: `nprocs`が1でない場合(つまり、複数のCPUコアが使用されている場合)、`cpus`変数に`"-`と`GOMAXPROCS`の値を組み合わせた文字列(例: `"-4"`)が設定されます。これにより、ベンチマーク名に`GOMAXPROCS`の値が付加されるようになります。
* **`fmt.Printf`の変更**: 既存の`fmt.Printf`呼び出しのフォーマット文字列に`%s`が追加され、`cpus`変数が挿入されるようになりました。これにより、出力される各統計情報(`Mem Alloc`, `ns/op`, `LastPause`, `Pause`, `Pause5`)の行に`GOMAXPROCS`の値が反映されます。
* 例: `garbage.BenchmarkNameMem Alloc=...` が `garbage.BenchmarkName-4Mem Alloc=...` のようになる。
この変更により、ガベージコレクションのベンチマーク結果が、`go test`コマンドの標準ベンチマーク出力形式と一致するようになり、異なる`GOMAXPROCS`設定下でのパフォーマンス比較が容易になります。
### `test/bench/garbage/tree2.go`の変更点
このファイルでは、`main`関数が変更され、`tree2`ベンチマークが標準ベンチマーク出力を生成するようになりました。
* **`import ("time")`**: `time`パッケージが新しくインポートされました。これは、ベンチマークの実行時間を測定するために必要です。
* **`const N = 10`**: ベンチマークのイテレーション回数を定義する定数`N`が追加されました。
* **`var t0 time.Time`**: ベンチマーク開始時刻を記録するための`time.Time`型の変数`t0`が宣言されました。
* **`t0 = time.Now()`**: `for`ループの直前で`time.Now()`を呼び出し、ベンチマークの開始時刻を`t0`に記録します。
* **`gcstats("BenchmarkTree2", N, time.Now().Sub(t0))`**: `for`ループの後に`gcstats`関数が呼び出されます。
* `"BenchmarkTree2"`: ベンチマークの名前。
* `N`: イテレーション回数(10)。
* `time.Now().Sub(t0)`: ベンチマークの総実行時間。`time.Now()`から`t0`を引くことで計算されます。
この変更により、`tree2`ベンチマークも`gcstats`関数を通じて、メモリ統計やGC一時停止時間などの詳細なパフォーマンスデータを標準ベンチマーク出力形式で報告するようになります。これにより、Goのビルドダッシュボードなどで`tree2`ベンチマークの結果が自動的に収集・分析できるようになります。
## 関連リンク
* Go CL 5992044: [https://golang.org/cl/5992044](https://golang.org/cl/5992044)
## 参考にした情報源リンク
* Go言語の`testing`パッケージ: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
* Go言語の`runtime`パッケージ: [https://pkg.go.dev/runtime](https://pkg.go.dev/runtime)
* Go言語の`GOMAXPROCS`に関するドキュメント (Goのバージョンによって異なる場合がありますが、一般的な概念は共通): [https://go.dev/doc/effective_go#concurrency](https://go.dev/doc/effective_go#concurrency) (Effective GoのConcurrencyセクション)
* Goのベンチマークの書き方 (Goのバージョンによって異なる場合がありますが、一般的な概念は共通): [https://go.dev/doc/code#benchmarking](https://go.dev/doc/code#benchmarking) (How to Write Go CodeのBenchmarkingセクション)
* Goのガベージコレクションに関する情報 (Goのバージョンによって異なる場合があります): [https://go.dev/doc/gc-guide](https://go.dev/doc/gc-guide) (Go GC Guide)
* Goの実験的なパッケージに関する情報 (`exp`パッケージ): [https://go.dev/doc/go1.0#exp](https://go.dev/doc/go1.0#exp) (Go 1 Release Notes - The exp repository)
* Goの`testing/script`パッケージに関する情報 (Goのバージョンによって異なる場合があります): [https://pkg.go.dev/testing/script](https://pkg.go.dev/testing/script) (GoDoc for testing/script, if it exists for a relevant Go version)