[インデックス 14899] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtesting
パッケージに、ベンチマーク実行時のメモリ割り当て統計をより柔軟に報告するための新機能(*B).ReportAllocs()
を導入するものです。これにより、個々のベンチマーク関数が自身のメモリ割り当て情報を報告するかどうかを制御できるようになり、グローバルな-test.benchmem
フラグに依存することなく、特定のベンチマークのメモリプロファイリングが可能になります。
コミット
commit 1e095b76229a8faa329dbdee6de246faabc3cf53
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu Jan 17 18:45:49 2013 +0800
testing: introduce (*B).ReportAllocs()
Calling it will show memory allocation statistics for that
single benchmark (if -test.benchmem is not provided)
R=golang-dev, rsc, kevlar, bradfitz
CC=golang-dev
https://golang.org/cl/7027046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1e095b76229a8faa329dbdee6de246faabc3cf53
元コミット内容
testing: introduce (*B).ReportAllocs()
Calling it will show memory allocation statistics for that
single benchmark (if -test.benchmem is not provided)
(日本語訳)
testing: (*B).ReportAllocs() を導入
これを呼び出すと、その単一のベンチマークのメモリ割り当て統計が表示される
(-test.benchmem が提供されていない場合でも)
変更の背景
Go言語のベンチマーク機能は、コードのパフォーマンスを測定するために非常に強力なツールです。しかし、メモリ割り当ての統計(例えば、操作あたりの割り当てバイト数や割り当て回数)を報告するには、通常go test
コマンドに-test.benchmem
フラグを付与する必要がありました。このフラグはグローバルに適用されるため、すべてのベンチマークに対してメモリ統計が報告されます。
しかし、開発者が特定のベンチマーク関数のみのメモリ割り当てを詳細に分析したい場合や、一部のベンチマークはメモリ割り当てが重要ではないため、その統計表示がノイズとなる場合がありました。また、既存のベンチマークコードでは、runtime.ReadMemStats()
関数を使用して手動でメモリ統計を収集し、b.Logf()
で出力するという冗長なパターンが見られました。
このコミットは、これらの課題を解決し、ベンチマークのメモリプロファイリングをより柔軟かつ簡潔に行えるようにすることを目的としています。具体的には、個々のベンチマーク関数が自身でメモリ割り当て統計の報告を有効にできるメカニズムを提供します。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とtesting
パッケージの知識が必要です。
-
Go言語のベンチマーク:
- Go言語では、
testing
パッケージを使用してベンチマークテストを記述します。 - ベンチマーク関数は
Benchmark
で始まり、*testing.B
型の引数を取ります(例:func BenchmarkMyFunction(b *testing.B)
)。 b.N
はベンチマークが実行されるイテレーション数を示し、ベンチマークコードはfor i := 0; i < b.N; i++
ループ内で実行されます。b.ResetTimer()
は、ベンチマークの計測を開始する前にセットアップコードの時間をリセットするために使用されます。b.SetBytes(n int64)
は、ベンチマークが処理するバイト数を設定し、結果にMB/sの統計を追加します。go test -bench=.
コマンドでベンチマークを実行します。
- Go言語では、
-
メモリ割り当て統計 (
-test.benchmem
):go test -bench=. -test.benchmem
のように-test.benchmem
フラグを付けてベンチマークを実行すると、各操作あたりのメモリ割り当て回数(allocs/op)と割り当てバイト数(B/op)が結果に表示されます。これは、コードのヒープ割り当てを特定し、最適化するのに役立ちます。
-
runtime
パッケージとMemStats
:runtime
パッケージは、Goランタイムとのインタラクションを提供します。runtime.ReadMemStats(m *MemStats)
関数は、現在のメモリ統計をMemStats
構造体に読み込みます。runtime.MemStats
構造体には、Mallocs
(割り当てられたオブジェクトの総数)やTotalAlloc
(割り当てられたバイトの総数)などのフィールドが含まれており、これらを手動で読み取ることでメモリ使用量を追跡できます。
-
testing.B
構造体:testing.B
はベンチマーク関数に渡される構造体で、ベンチマークの実行制御や結果報告のためのメソッドを提供します。このコミットでは、この構造体に新しいフィールドとメソッドが追加されます。
技術的詳細
このコミットの技術的な核心は、testing.B
構造体に新しい状態と、その状態を操作するメソッドを追加し、ベンチマーク結果の報告ロジックを拡張することにあります。
-
testing.B
構造体へのshowAllocResult
フィールドの追加:src/pkg/testing/benchmark.go
内のtype B struct
に、showAllocResult bool
という新しいフィールドが追加されました。このブーリアン値は、その特定のB
インスタンス(つまり、特定のベンチマーク関数)に対してメモリ割り当て統計を報告すべきかどうかを示すフラグとして機能します。
-
(*B).ReportAllocs()
メソッドの導入:testing.B
型にReportAllocs()
という新しいメソッドが追加されました。このメソッドは非常にシンプルで、単にb.showAllocResult = true
を設定します。- このメソッドをベンチマーク関数内で呼び出すことで、開発者はそのベンチマークに対して明示的にメモリ割り当て統計の報告を要求できます。コミットメッセージにあるように、これはグローバルな
-test.benchmem
フラグが設定されていない場合でも機能します。
-
RunBenchmarks
関数における報告ロジックの変更:src/pkg/testing/benchmark.go
内のRunBenchmarks
関数は、ベンチマークの実行と結果の出力を行う主要な関数です。- この関数内で、メモリ統計を結果文字列に追加する条件が変更されました。以前は
*benchmarkMemory
(これは-test.benchmem
フラグに対応する内部変数)のみをチェックしていましたが、変更後は*benchmarkMemory || b.showAllocResult
をチェックするようになりました。 - これにより、グローバルフラグが設定されているか、または個々のベンチマークが
ReportAllocs()
を呼び出したかのいずれかの条件が満たされれば、メモリ割り当て統計が報告されるようになります。
-
既存のベンチマークコードの簡素化:
src/pkg/exp/html/parse_test.go
とsrc/pkg/exp/html/token_test.go
の既存のベンチマーク関数(BenchmarkParser
とbenchmarkTokenizer
)は、以前はruntime.ReadMemStats()
を使って手動でメモリ割り当てを計測し、b.Logf()
で出力していました。- この手動での計測コードはすべて削除され、代わりに
b.ReportAllocs()
の呼び出しに置き換えられました。これにより、ベンチマークコードが大幅に簡潔になり、testing
パッケージの新しいイディオムに準拠するようになりました。
この変更により、Goのベンチマークシステムは、メモリプロファイリングの柔軟性と使いやすさの両方を向上させました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下の3つのファイルに集中しています。
-
src/pkg/testing/benchmark.go
:B
構造体にshowAllocResult bool
フィールドが追加されました。B
構造体にReportAllocs()
メソッドが追加されました。RunBenchmarks
関数内のメモリ統計報告条件が*benchmarkMemory || b.showAllocResult
に変更されました。
-
src/pkg/exp/html/parse_test.go
:BenchmarkParser
関数から、runtime.MemStats
を使った手動のメモリ割り当て計測コードが削除されました。- 代わりに
b.ReportAllocs()
が呼び出されるようになりました。
-
src/pkg/exp/html/token_test.go
:benchmarkTokenizer
関数から、runtime.MemStats
を使った手動のメモリ割り当て計測コードが削除されました。- 代わりに
b.ReportAllocs()
が呼び出されるようになりました。
コアとなるコードの解説
src/pkg/testing/benchmark.go
の変更
--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -34,11 +34,12 @@ type InternalBenchmark struct {
// timing and to specify the number of iterations to run.
type B struct {
common
- N int
- benchmark InternalBenchmark
- bytes int64
- timerOn bool
- result BenchmarkResult
+ N int
+ benchmark InternalBenchmark
+ bytes int64
+ timerOn bool
+ showAllocResult bool // 新規追加
+ result BenchmarkResult
// The initial states of memStats.Mallocs and memStats.TotalAlloc.
startAllocs uint64
startBytes uint64
@@ -91,6 +92,13 @@ func (b *B) ResetTimer() {
// If this is called, the benchmark will report ns/op and MB/s.
func (b *B) SetBytes(n int64) { b.bytes = n }
+// ReportAllocs enables malloc statistics for this benchmark.
+// It is equivalent to setting -test.benchmem, but it only affects the
+// benchmark function that calls ReportAllocs.
+func (b *B) ReportAllocs() { // 新規追加メソッド
+ b.showAllocResult = true
+}
+
func (b *B) nsPerOp() int64 {
if b.N <= 0 {
return 0
@@ -298,7 +306,7 @@ func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks [
results := r.String()
- if *benchmarkMemory {
+ if *benchmarkMemory || b.showAllocResult { // 条件変更
results += "\t" + r.MemString()
}
fmt.Println(results)
B
構造体にshowAllocResult bool
が追加され、個別のベンチマークがメモリ統計を報告するかどうかを制御できるようになりました。ReportAllocs()
メソッドは、このshowAllocResult
フラグをtrue
に設定するシンプルな関数です。RunBenchmarks
関数では、メモリ統計の出力条件が*benchmarkMemory
(グローバルな-test.benchmem
フラグ)だけでなく、b.showAllocResult
も考慮するようになりました。これにより、グローバルフラグがなくても、ReportAllocs()
を呼び出したベンチマークはメモリ統計を報告します。
src/pkg/exp/html/parse_test.go
の変更
--- a/src/pkg/exp/html/parse_test.go
+++ b/src/pkg/exp/html/parse_test.go
@@ -382,15 +382,9 @@ func BenchmarkParser(b *testing.B) {
}\n \tb.SetBytes(int64(len(buf)))\n \truntime.GC()\n-\tvar ms runtime.MemStats\n-\truntime.ReadMemStats(&ms)\n-\tmallocs := ms.Mallocs\n+\tb.ReportAllocs()\n \tb.ResetTimer()\n \tfor i := 0; i < b.N; i++ {\n \t\tParse(bytes.NewBuffer(buf))\n \t}\n-\tb.StopTimer()\n-\truntime.ReadMemStats(&ms)\n-\tmallocs = ms.Mallocs - mallocs\n-\tb.Logf(\"%d iterations, %d mallocs per iteration\\n\", b.N, int(mallocs)/b.N)\n }\n```
* 手動で`runtime.MemStats`を読み取り、`mallocs`を計算して`b.Logf`で出力していた冗長なコードが削除されました。
* 代わりに、新しく導入された`b.ReportAllocs()`が呼び出されています。これにより、このベンチマークのメモリ割り当て統計が自動的に報告されるようになります。
### `src/pkg/exp/html/token_test.go` の変更
```diff
--- a/src/pkg/exp/html/token_test.go
+++ b/src/pkg/exp/html/token_test.go
@@ -634,9 +634,7 @@ func benchmarkTokenizer(b *testing.B, level int) {
}\n \tb.SetBytes(int64(len(buf)))\n \truntime.GC()\n-\tvar ms runtime.MemStats\n-\truntime.ReadMemStats(&ms)\n-\tmallocs := ms.Mallocs\n+\tb.ReportAllocs()\n \tb.ResetTimer()\n \tfor i := 0; i < b.N; i++ {\n \t\tz := NewTokenizer(bytes.NewBuffer(buf))\n@@ -674,10 +672,6 @@ func benchmarkTokenizer(b *testing.B, level int) {\n \t\t\t}\n \t\t}\n \t}\n-\tb.StopTimer()\n-\truntime.ReadMemStats(&ms)\n-\tmallocs = ms.Mallocs - mallocs\n-\tb.Logf(\"%d iterations, %d mallocs per iteration\\n\", b.N, int(mallocs)/b.N)\n }\n```
* `parse_test.go`と同様に、`benchmarkTokenizer`関数からも手動のメモリ割り当て計測コードが削除され、`b.ReportAllocs()`に置き換えられました。
これらの変更により、Goのベンチマークにおけるメモリプロファイリングのメカニズムが改善され、よりクリーンで柔軟なAPIが提供されるようになりました。
## 関連リンク
* Go言語の`testing`パッケージドキュメント: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
* Go言語のベンチマークに関する公式ブログ記事 (古いものですが概念は共通): [https://go.dev/blog/benchmarking](https://go.dev/blog/benchmarking)
* Go言語の`runtime`パッケージドキュメント: [https://pkg.go.dev/runtime](https://pkg.go.dev/runtime)
## 参考にした情報源リンク
* Go言語の公式ドキュメント
* Go言語のソースコード(特に`src/pkg/testing/benchmark.go`)
* コミットメッセージと関連するGo CL (Change-list) の情報
* Go言語のベンチマークに関する一般的な知識とプラクティス