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

[インデックス 17891] ファイルの概要

このコミットは、Go言語のパフォーマンスベンチマークフレームワークに対する重要な改善を含んでいます。具体的には、test/bench/perf/bench1.gotest/bench/perf/driver.go の2つのファイルが変更されています。bench1.go は新しいベンチマークフレームワークを利用するように更新されたサンプルベンチマークであり、driver.go はその新しいベンチマークフレームワークのコアロジックを定義しています。この変更により、ベンチマークの実行と結果の収集がより柔軟になり、特にメモリ使用量などの追加メトリクスを測定できるようになりました。

コミット

commit aa9d2cb2c77e7fa4389cebe9b99d953f9c0c478b
Author: dvyukov <dvyukov@google.com>
Date:   Tue Nov 19 15:31:01 2013 +0400

    -
---
 test/bench/perf/bench1.go | 10 +++++---
 test/bench/perf/driver.go | 58 +++++++++++++++++++++++++++++++++--------------
 2 files changed, 48 insertions(+), 20 deletions(-)

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/aa9d2cb2c77e7fa4389cebe9b99d953f9c0c478b

元コミット内容

コミットメッセージは非常に簡潔に「-」とだけ記されています。これは、このコミットが特定のバグ修正や機能追加というよりも、既存のベンチマークインフラストラクチャの内部的な改善やリファクタリングであることを示唆しています。実際の変更内容から、パフォーマンスベンチマークの実行とメトリクス収集のメカニズムが大幅に強化されたことが読み取れます。

変更の背景

Go言語の標準ライブラリには、testing パッケージによるベンチマーク機能が組み込まれていますが、このコミットは test/bench/perf ディレクトリ内のより低レベルなパフォーマンスベンチマークフレームワークに対するものです。従来のベンチマークは主に実行時間のみを測定していましたが、より包括的なパフォーマンス分析のためには、メモリ使用量やその他のカスタムメトリクスも同時に測定できる機能が求められていました。

この変更の背景には、Goランタイムのパフォーマンス特性をより深く理解し、最適化を進めるためのニーズがあったと考えられます。特に、ガベージコレクション(GC)の挙動やメモリ割り当てがパフォーマンスに与える影響を正確に評価するためには、実行時間だけでなく、詳細なメモリ統計を収集できる仕組みが不可欠でした。このコミットは、そのような高度なパフォーマンスプロファイリングを可能にするための基盤を構築しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と標準ライブラリの知識が必要です。

  • Go言語のベンチマーク: Go言語には go test -bench コマンドを通じて実行できるベンチマーク機能が組み込まれています。これは testing パッケージによって提供され、BenchmarkXxx という関数名で定義されます。通常、ベンチマーク関数は b *testing.B を引数にとり、b.N 回の操作を実行します。
  • time パッケージ: 時間の測定と操作を提供します。time.Now() で現在の時刻を取得し、time.Since() で経過時間を計算します。time.Duration は時間の長さを表す型です。
  • flag パッケージ: コマンドライン引数をパースするための機能を提供します。flag.Parse() を呼び出すことで、定義されたフラグがコマンドラインから読み込まれます。
  • runtime パッケージ: Goランタイムとのインタラクションを提供します。
    • runtime.GC(): ガベージコレクタを強制的に実行します。ベンチマークの前にGCを実行することで、以前のテストやベンチマークの残骸が現在の測定に影響を与えるのを防ぎ、より安定した結果を得ることができます。
    • runtime.MemStats: Goプログラムのメモリ割り当てに関する統計情報を含む構造体です。ヒープの使用量、GCの回数、一時オブジェクトの数など、詳細なメモリプロファイル情報を提供します。
    • runtime.ReadMemStats(m *MemStats): 現在のメモリ統計を MemStats 構造体に読み込みます。
  • log パッケージ: ログメッセージを出力するための機能を提供します。log.Printf はフォーマットされた文字列を標準エラー出力に書き込みます。log.Fatalf はフォーマットされたエラーメッセージを出力し、プログラムを終了させます。
  • fmt パッケージ: フォーマットされたI/Oを提供します。fmt.Printf はフォーマットされた文字列を標準出力に書き込みます。
  • GOPERF-METRIC: Goのパフォーマンスベンチマークツールが結果をパースするために使用する、特定の出力フォーマットです。この形式で出力された行は、ベンチマーク結果として認識され、集計されます。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. 汎用的なベンチマーク関数の導入 (BenchFunc):

    • driver.gotype BenchFunc func(N int64) ([]PerfMetric, error) という新しい型が定義されました。これにより、ベンチマーク関数は実行時間だけでなく、PerfMetric のスライスを返すことができるようになりました。これは、カスタムメトリクスをベンチマーク結果に含めるための重要な変更です。
    • bench1.goBenchmark 関数は削除され、新しい SleepBenchmark 関数が BenchFunc のシグネチャに合わせて定義されました。この関数は、単にスリープするだけでなく、PerfMetric{"foo", 42} というダミーのメトリクスを返すようになりました。
  2. ベンチマーク結果構造体の拡張 (PerfResult, PerfMetric):

    • driver.goResult 構造体が PerfResult にリネームされ、Metrics []PerfMetric フィールドが追加されました。これにより、実行時間 (RunTime) とイテレーション数 (N) に加えて、カスタムメトリクスも結果として保持できるようになりました。
    • PerfMetric 構造体 (Type string, Val int64) が新しく定義され、名前と値のペアでカスタムメトリクスを表現します。
  3. ベンチマークドライバの強化 (PerfBenchmark, RunBenchmark, RunOnce):

    • PerfBenchmark(f BenchFunc) 関数が導入され、main 関数から呼び出されるようになりました。この関数は、与えられた BenchFunc を複数回実行し、最も良い結果(実行時間が短いもの)を選択します。
    • RunBenchmark 関数は BenchFunc を引数にとるように変更され、内部で RunOnce を呼び出す際にこの関数を渡すようになりました。
    • RunOnce(f BenchFunc, N int64) PerfResult 関数が大幅に強化されました。
      • GCの強制実行: runtime.GC() がベンチマークの直前に呼び出されるようになりました。これにより、ベンチマークの実行前にガベージコレクタがメモリをクリーンアップし、より正確で再現性の高いパフォーマンス測定が可能になります。
      • メモリ統計の収集: runtime.ReadMemStats を使用して、ベンチマーク実行前後のメモリ統計 (mstats0, mstats1) が収集されるようになりました。これにより、ベンチマークがメモリ使用量に与える影響を詳細に分析できます。
      • カスタムメトリクスの取得: 渡された BenchFunc を実行し、その戻り値である []PerfMetricPerfResult に格納します。
      • ログ出力の改善: fmt.Printfos.Exit の代わりに log.Printflog.Fatalf が使用されるようになりました。これにより、ベンチマークの実行中のデバッグ情報がより適切に管理され、エラー発生時の挙動も改善されます。
  4. GOPERF-METRIC 出力の拡張:

    • PerfBenchmark 関数内で、カスタムメトリクスも fmt.Printf("GOPERF-METRIC:%v=%v\\n", m.Type, m.Val) の形式で出力されるようになりました。これにより、ベンチマークツールが実行時間だけでなく、カスタムメトリクスも自動的にパースして集計できるようになります。

これらの変更により、Goのパフォーマンスベンチマークは、単なる実行時間の測定から、メモリプロファイリングやカスタムメトリクスを含む、より包括的なパフォーマンス分析ツールへと進化しました。

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

test/bench/perf/bench1.go

--- a/test/bench/perf/bench1.go
+++ b/test/bench/perf/bench1.go
@@ -4,8 +4,12 @@ import (
 	"time"
 )
 
-func Benchmark(N int64) error {
-	// 13+
+func main() {
+	PerfBenchmark(SleepBenchmark)
+}
+
+func SleepBenchmark(N int64) (metrics []PerfMetric, err error) {
 	time.Sleep(time.Duration(N) * time.Millisecond)
-	return nil
+	metrics = append(metrics, PerfMetric{"foo", 42})
+	return
 }

test/bench/perf/driver.go

--- a/test/bench/perf/driver.go
+++ b/test/bench/perf/driver.go
@@ -3,8 +3,9 @@ package main
 import (
 	"flag"
 	"fmt"
-	"os"
+	"log"
 	"time"
+	"runtime"
 )
 
 var (
@@ -13,45 +14,68 @@ var (
 	benchMem  = flag.Int("benchmem", 64, "approx RSS value to aim at in benchmarks, in MB")
 )
 
-type Result struct {
+type PerfResult struct {
 	N       int64
 	RunTime time.Duration
+	Metrics []PerfMetric
 }
 
-func main() {
-	flag.Parse()
-	var res Result
+type PerfMetric struct {
+	Type  string
+	Val   int64
+}
+
+type BenchFunc func(N int64) ([]PerfMetric, error)
+
+func PerfBenchmark(f BenchFunc) {
+	if !flag.Parsed() {
+		flag.Parse()
+	}
+	var res PerfResult
 	for i := 0; i < *benchNum; i++ {
-		res1 := RunBenchmark()
+		res1 := RunBenchmark(f)
 		if res.RunTime == 0 || res.RunTime > res1.RunTime {
 			res = res1
 		}
 	}
 	fmt.Printf("GOPERF-METRIC:runtime=%v\n", int64(res.RunTime)/res.N)
+	for _, m := range res.Metrics {
+		fmt.Printf("GOPERF-METRIC:%v=%v\n", m.Type, m.Val)
+	}
 }
 
-func RunBenchmark() Result {
-	var res Result
+func RunBenchmark(f BenchFunc) PerfResult {
+	var res PerfResult
 	for ChooseN(&res) {
-		res = RunOnce(res.N)
+		log.Printf("Benchmarking %v iterations\n", res.N)
+		res = RunOnce(f, res.N)
+		log.Printf("Done: %+v\n", res)
 	}
 	return res
 }
 
-func RunOnce(N int64) Result {
-	fmt.Printf("Benchmarking %v iterations\n", N)
+func RunOnce(f BenchFunc, N int64) PerfResult {
+	runtime.GC()
+	mstats0 := new(runtime.MemStats)
+	runtime.ReadMemStats(mstats0)
+	res := PerfResult{N: N}
+
 	t0 := time.Now()
-\terr := Benchmark(N)
+	var err error
+	res.Metrics, err = f(N)
+	res.RunTime = time.Since(t0)
+
 	if err != nil {
-\t\tfmt.Printf("Benchmark function failed: %v\\n", err)\n-\t\tos.Exit(1)\n+\t\tlog.Fatalf("Benchmark function failed: %v\\n", err)
 	}
-\tres := Result{N: N}\n-\tres.RunTime = time.Since(t0)\n+\n+	mstats1 := new(runtime.MemStats)
+	mstats1 := new(runtime.MemStats)
+	runtime.ReadMemStats(mstats1)
+	fmt.Printf("%+v\\n", *mstats1)
 	return res
 }
 
-func ChooseN(res *Result) bool {
+func ChooseN(res *PerfResult) bool {
 	const MaxN = 1e12
 	last := res.N
 	if last == 0 {

コアとなるコードの解説

このコミットの核心は、Goのパフォーマンスベンチマークの柔軟性と測定能力を向上させることにあります。

  • bench1.go の変更:

    • Benchmark 関数が削除され、main 関数と SleepBenchmark 関数が追加されました。これは、従来の固定的なベンチマーク関数から、新しい汎用的な PerfBenchmark ドライバに適合する形式への移行を示しています。
    • SleepBenchmarkPerfMetric を返すようになったことで、ベンチマークが実行時間以外の情報も報告できるようになったことが明確に示されています。
  • driver.go の変更:

    • PerfResultPerfMetric: ベンチマーク結果を格納する構造体が拡張され、カスタムメトリクスを保持できるようになりました。これにより、実行時間だけでなく、メモリ使用量やその他のアプリケーション固有の統計情報もベンチマーク結果の一部として扱えるようになります。
    • BenchFunc: ベンチマーク関数を抽象化する新しい型が導入されました。これにより、driver.go は特定のベンチマーク実装に依存せず、任意の BenchFunc を実行できる汎用的なベンチマークドライバとして機能するようになります。これは、フレームワークのモジュール性と再利用性を高めます。
    • PerfBenchmark: 新しいエントリポイントとして機能し、複数のベンチマーク実行を管理し、最適な結果を選択します。また、カスタムメトリクスを含むすべての結果を GOPERF-METRIC 形式で出力することで、外部のツールがこれらの結果を容易にパースできるようにします。
    • RunOnce の強化:
      • runtime.GC() の呼び出しは、ベンチマークの測定精度を向上させるための重要なステップです。これにより、ベンチマークの開始時にGCが完了していることが保証され、GCの実行がベンチマークの実行時間に不規則な影響を与えるのを防ぎます。
      • runtime.ReadMemStats によるメモリ統計の収集は、このコミットの最も重要な追加機能の一つです。これにより、ベンチマークが実行中にどれだけのメモリを割り当て、解放したか、GCがどのように動作したかなど、詳細なメモリプロファイル情報を取得できるようになります。これは、メモリリークの特定やメモリ効率の最適化に不可欠な情報です。
      • log.Printf の使用は、デバッグ情報の出力方法を改善し、より構造化されたログを生成します。

これらの変更は、Goのパフォーマンスベンチマークインフラストラクチャをより堅牢で、多機能で、分析能力の高いものにするための基盤を築いています。特に、メモリプロファイリング機能の追加は、Goプログラムのパフォーマンス最適化において非常に価値のあるものです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記関連リンクに記載)
  • Go言語のベンチマークに関する一般的な情報源 (例: Go blog, 各種技術記事)
  • runtime.MemStats の詳細に関する情報源 (例: Goのソースコード、関連するIssueやDesign Document)
    • Go runtime source code (specifically runtime/mstats.go and runtime/mgc.go for MemStats and GC details)
    • Go issue tracker for discussions around performance and memory profiling.