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

[インデックス 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パッケージの知識が必要です。

  1. 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=.コマンドでベンチマークを実行します。
  2. メモリ割り当て統計 (-test.benchmem):

    • go test -bench=. -test.benchmemのように-test.benchmemフラグを付けてベンチマークを実行すると、各操作あたりのメモリ割り当て回数(allocs/op)と割り当てバイト数(B/op)が結果に表示されます。これは、コードのヒープ割り当てを特定し、最適化するのに役立ちます。
  3. runtimeパッケージとMemStats:

    • runtimeパッケージは、Goランタイムとのインタラクションを提供します。
    • runtime.ReadMemStats(m *MemStats)関数は、現在のメモリ統計をMemStats構造体に読み込みます。
    • runtime.MemStats構造体には、Mallocs(割り当てられたオブジェクトの総数)やTotalAlloc(割り当てられたバイトの総数)などのフィールドが含まれており、これらを手動で読み取ることでメモリ使用量を追跡できます。
  4. testing.B構造体:

    • testing.Bはベンチマーク関数に渡される構造体で、ベンチマークの実行制御や結果報告のためのメソッドを提供します。このコミットでは、この構造体に新しいフィールドとメソッドが追加されます。

技術的詳細

このコミットの技術的な核心は、testing.B構造体に新しい状態と、その状態を操作するメソッドを追加し、ベンチマーク結果の報告ロジックを拡張することにあります。

  1. testing.B構造体へのshowAllocResultフィールドの追加:

    • src/pkg/testing/benchmark.go内のtype B structに、showAllocResult boolという新しいフィールドが追加されました。このブーリアン値は、その特定のBインスタンス(つまり、特定のベンチマーク関数)に対してメモリ割り当て統計を報告すべきかどうかを示すフラグとして機能します。
  2. (*B).ReportAllocs()メソッドの導入:

    • testing.B型にReportAllocs()という新しいメソッドが追加されました。このメソッドは非常にシンプルで、単にb.showAllocResult = trueを設定します。
    • このメソッドをベンチマーク関数内で呼び出すことで、開発者はそのベンチマークに対して明示的にメモリ割り当て統計の報告を要求できます。コミットメッセージにあるように、これはグローバルな-test.benchmemフラグが設定されていない場合でも機能します。
  3. RunBenchmarks関数における報告ロジックの変更:

    • src/pkg/testing/benchmark.go内のRunBenchmarks関数は、ベンチマークの実行と結果の出力を行う主要な関数です。
    • この関数内で、メモリ統計を結果文字列に追加する条件が変更されました。以前は*benchmarkMemory(これは-test.benchmemフラグに対応する内部変数)のみをチェックしていましたが、変更後は*benchmarkMemory || b.showAllocResultをチェックするようになりました。
    • これにより、グローバルフラグが設定されているか、または個々のベンチマークがReportAllocs()を呼び出したかのいずれかの条件が満たされれば、メモリ割り当て統計が報告されるようになります。
  4. 既存のベンチマークコードの簡素化:

    • src/pkg/exp/html/parse_test.gosrc/pkg/exp/html/token_test.goの既存のベンチマーク関数(BenchmarkParserbenchmarkTokenizer)は、以前はruntime.ReadMemStats()を使って手動でメモリ割り当てを計測し、b.Logf()で出力していました。
    • この手動での計測コードはすべて削除され、代わりにb.ReportAllocs()の呼び出しに置き換えられました。これにより、ベンチマークコードが大幅に簡潔になり、testingパッケージの新しいイディオムに準拠するようになりました。

この変更により、Goのベンチマークシステムは、メモリプロファイリングの柔軟性と使いやすさの両方を向上させました。

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

このコミットにおける主要なコード変更は以下の3つのファイルに集中しています。

  1. src/pkg/testing/benchmark.go:

    • B構造体にshowAllocResult boolフィールドが追加されました。
    • B構造体にReportAllocs()メソッドが追加されました。
    • RunBenchmarks関数内のメモリ統計報告条件が*benchmarkMemory || b.showAllocResultに変更されました。
  2. src/pkg/exp/html/parse_test.go:

    • BenchmarkParser関数から、runtime.MemStatsを使った手動のメモリ割り当て計測コードが削除されました。
    • 代わりにb.ReportAllocs()が呼び出されるようになりました。
  3. 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言語のベンチマークに関する一般的な知識とプラクティス