[インデックス 17891] ファイルの概要
このコミットは、Go言語のパフォーマンスベンチマークフレームワークに対する重要な改善を含んでいます。具体的には、test/bench/perf/bench1.go
と test/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のパフォーマンスベンチマークツールが結果をパースするために使用する、特定の出力フォーマットです。この形式で出力された行は、ベンチマーク結果として認識され、集計されます。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
汎用的なベンチマーク関数の導入 (
BenchFunc
):driver.go
にtype BenchFunc func(N int64) ([]PerfMetric, error)
という新しい型が定義されました。これにより、ベンチマーク関数は実行時間だけでなく、PerfMetric
のスライスを返すことができるようになりました。これは、カスタムメトリクスをベンチマーク結果に含めるための重要な変更です。bench1.go
のBenchmark
関数は削除され、新しいSleepBenchmark
関数がBenchFunc
のシグネチャに合わせて定義されました。この関数は、単にスリープするだけでなく、PerfMetric{"foo", 42}
というダミーのメトリクスを返すようになりました。
-
ベンチマーク結果構造体の拡張 (
PerfResult
,PerfMetric
):driver.go
のResult
構造体がPerfResult
にリネームされ、Metrics []PerfMetric
フィールドが追加されました。これにより、実行時間 (RunTime
) とイテレーション数 (N
) に加えて、カスタムメトリクスも結果として保持できるようになりました。PerfMetric
構造体 (Type string
,Val int64
) が新しく定義され、名前と値のペアでカスタムメトリクスを表現します。
-
ベンチマークドライバの強化 (
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
を実行し、その戻り値である[]PerfMetric
をPerfResult
に格納します。 - ログ出力の改善:
fmt.Printf
とos.Exit
の代わりにlog.Printf
とlog.Fatalf
が使用されるようになりました。これにより、ベンチマークの実行中のデバッグ情報がより適切に管理され、エラー発生時の挙動も改善されます。
- GCの強制実行:
-
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
ドライバに適合する形式への移行を示しています。SleepBenchmark
がPerfMetric
を返すようになったことで、ベンチマークが実行時間以外の情報も報告できるようになったことが明確に示されています。
-
driver.go
の変更:PerfResult
とPerfMetric
: ベンチマーク結果を格納する構造体が拡張され、カスタムメトリクスを保持できるようになりました。これにより、実行時間だけでなく、メモリ使用量やその他のアプリケーション固有の統計情報もベンチマーク結果の一部として扱えるようになります。BenchFunc
: ベンチマーク関数を抽象化する新しい型が導入されました。これにより、driver.go
は特定のベンチマーク実装に依存せず、任意のBenchFunc
を実行できる汎用的なベンチマークドライバとして機能するようになります。これは、フレームワークのモジュール性と再利用性を高めます。PerfBenchmark
: 新しいエントリポイントとして機能し、複数のベンチマーク実行を管理し、最適な結果を選択します。また、カスタムメトリクスを含むすべての結果をGOPERF-METRIC
形式で出力することで、外部のツールがこれらの結果を容易にパースできるようにします。RunOnce
の強化:runtime.GC()
の呼び出しは、ベンチマークの測定精度を向上させるための重要なステップです。これにより、ベンチマークの開始時にGCが完了していることが保証され、GCの実行がベンチマークの実行時間に不規則な影響を与えるのを防ぎます。runtime.ReadMemStats
によるメモリ統計の収集は、このコミットの最も重要な追加機能の一つです。これにより、ベンチマークが実行中にどれだけのメモリを割り当て、解放したか、GCがどのように動作したかなど、詳細なメモリプロファイル情報を取得できるようになります。これは、メモリリークの特定やメモリ効率の最適化に不可欠な情報です。log.Printf
の使用は、デバッグ情報の出力方法を改善し、より構造化されたログを生成します。
これらの変更は、Goのパフォーマンスベンチマークインフラストラクチャをより堅牢で、多機能で、分析能力の高いものにするための基盤を築いています。特に、メモリプロファイリング機能の追加は、Goプログラムのパフォーマンス最適化において非常に価値のあるものです。
関連リンク
- Go言語の
testing
パッケージ: https://pkg.go.dev/testing - Go言語の
runtime
パッケージ: https://pkg.go.dev/runtime - Go言語の
flag
パッケージ: https://pkg.go.dev/flag - Go言語の
time
パッケージ: https://pkg.go.dev/time - Go言語の
log
パッケージ: https://pkg.go.dev/log
参考にした情報源リンク
- Go言語の公式ドキュメント (上記関連リンクに記載)
- Go言語のベンチマークに関する一般的な情報源 (例: Go blog, 各種技術記事)
- A brief tour of Go's new
testing
package: https://go.dev/blog/testing - Go Benchmarking: https://go.dev/doc/articles/go_benchmarking.html
- A brief tour of Go's new
runtime.MemStats
の詳細に関する情報源 (例: Goのソースコード、関連するIssueやDesign Document)- Go runtime source code (specifically
runtime/mstats.go
andruntime/mgc.go
forMemStats
and GC details) - Go issue tracker for discussions around performance and memory profiling.
- Go runtime source code (specifically