[インデックス 14306] ファイルの概要
このコミットは、Go言語のベンチマーク結果を比較するためのユーティリティツールである misc/benchcmp
に、メモリ統計(アロケーション数)を表示する機能を追加するものです。これにより、ベンチマーク実行時に -test.benchmem
フラグを使用した場合に、操作あたりのメモリ割り当て数(allocs/op)の変化も比較できるようになります。
コミット
commit d5449eafb95cf6b622ad7f569c88e596f9a710a3
Author: Jeff R. Allen <jra@nella.org>
Date: Sat Nov 3 02:13:51 2012 +0800
misc/benchcmp: show memory statistics, when available
R=minux.ma, dave, extraterrestrial.neighbour, rsc
CC=golang-dev
https://golang.org/cl/6587069
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d5449eafb95cf6b622ad7f569c88e596f9a710a3
元コミット内容
misc/benchcmp: show memory statistics, when available
このコミットは、misc/benchcmp
ツールが利用可能な場合にメモリ統計を表示するように変更します。
変更の背景
Go言語のベンチマークツール go test -bench
は、関数の実行速度(ns/op
)を測定するだけでなく、-benchmem
フラグを付与することで、操作あたりのメモリ割り当て量(B/op
)とメモリ割り当て回数(allocs/op
)も報告できます。
しかし、このコミットが導入される以前の benchcmp
ツールは、主に ns/op
の比較に特化しており、go test -benchmem
で出力されるメモリ関連の統計情報を適切に解析し、比較する機能がありませんでした。パフォーマンスの最適化において、単に実行速度だけでなく、メモリ使用量やアロケーション回数を削減することも非常に重要です。特にガベージコレクションのオーバーヘッドを減らすためには、アロケーション回数の変化を追跡することが不可欠です。
このため、開発者がコード変更によるメモリパフォーマンスへの影響をより詳細に把握できるように、benchcmp
がメモリ統計も比較表示できるよう機能拡張する必要がありました。
前提知識の解説
Go言語のベンチマーク (go test -bench
)
Go言語には、標準ライブラリの testing
パッケージにベンチマーク機能が組み込まれています。ベンチマーク関数は Benchmark
プレフィックスで始まり、*testing.B
型の引数を取ります。
package mypackage
import "testing"
func BenchmarkMyFunction(b *testing.B) {
for i := 0; i < b.N; i++ {
// ベンチマーク対象のコード
}
}
ベンチマークは go test -bench=.
コマンドで実行できます。このコマンドは、パッケージ内のすべてのベンチマーク関数を実行し、各操作にかかる平均時間(ns/op
)を出力します。
メモリ統計のベンチマーク (-benchmem
)
go test
コマンドに -benchmem
フラグを追加すると、ベンチマーク結果にメモリ割り当てに関する統計情報が追加されます。
例: go test -bench=. -benchmem
出力例:
BenchmarkMyFunction-8 100000000 12.3 ns/op 64 B/op 2 allocs/op
ns/op
: 1操作あたりのナノ秒。B/op
: 1操作あたりのバイト割り当て量。allocs/op
: 1操作あたりのメモリ割り当て回数。
このコミットは、特に allocs/op
の比較に焦点を当てています。
benchcmp
ツール
benchcmp
は、Go言語のベンチマーク結果を比較するためのシンプルなコマンドラインツールです。通常、コード変更前と変更後のベンチマーク結果ファイル(old.txt
と new.txt
など)を引数として受け取り、それぞれのベンチマークの ns/op
を比較し、改善率や悪化率を表示します。
例: benchcmp old.txt new.txt
このツールは、go test
の出力形式を解析し、ベンチマーク名に基づいて対応する結果を突き合わせます。
awk
コマンドとフィールド処理
benchcmp
ツールは、シェルスクリプトとして実装されており、内部で awk
コマンドを多用して go test
の出力を解析しています。
awk
は、テキストファイルを処理するための強力なツールです。デフォルトでは、空白文字で区切られた各行の要素を「フィールド」として扱います。
$0
: 行全体$1
: 1番目のフィールド$2
: 2番目のフィールド- ...
$N
: N番目のフィールド
awk
では、正規表現を使って特定のフィールドの内容をチェックできます。
$N ~ /pattern/
: N番目のフィールドがpattern
にマッチする場合。
go test -benchmem
の出力では、allocs/op
の値が特定のフィールド(例えば $8
や $10
)に現れることがあります。これは、ベンチマーク関数が b.SetBytes()
を使用しているかどうかによって、出力されるフィールドの数が変動するためです。このコミットでは、このフィールドの変動に対応するために $8
と $10
の両方をチェックするロジックが追加されています。
技術的詳細
このコミットの主要な目的は、benchcmp
ツールが go test -benchmem
の出力からメモリ割り当て回数(allocs/op
)を抽出し、比較表示できるようにすることです。
benchcmp
は awk
スクリプトを使用して go test
の出力を解析します。go test -benchmem
の出力行は、以下のような形式を取ります。
BenchmarkName-N Iterations Time ns/op Bytes B/op Allocs allocs/op
ここで、Bytes B/op
の部分 (MB/s
の場合もある) が存在するかどうかによって、allocs/op
の値が異なるフィールド番号に現れる可能性があります。
b.SetBytes()
を使用しない場合:allocs/op
は$8
に現れることが多い。b.SetBytes()
を使用する場合:MB/s
のフィールドが追加されるため、allocs/op
は$10
に現れることが多い。
このコミットでは、この変動に対応するため、awk
スクリプト内で $8
と $10
の両方をチェックし、allocs/op
の値が存在すればそれを取得するロジックが追加されました。
具体的には、newalloc
と oldalloc
という連想配列(awk
の配列)が導入され、ベンチマーク名($1
)をキーとして、それぞれのベンチマークの allocs/op
の値を格納します。
最終的な出力フェーズでは、これらの newalloc
と oldalloc
の値を使用して、ns/op
や MB/s
と同様に、allocs/op
の「old」「new」「delta」を計算し、整形された形式で表示します。delta
はパーセンテージで表示され、oldalloc[what] == 0
の場合は n/a
と表示されるように考慮されています。
コアとなるコードの変更箇所
misc/benchcmp
ファイルに対する変更は以下の通りです。
--- a/misc/benchcmp
+++ b/misc/benchcmp
@@ -7,8 +7,13 @@ case "$1" in
-*)
echo 'usage: benchcmp old.txt new.txt' >&2
echo >&2
- echo 'Each input file should be go test -test.run=NONE -test.bench=. > [old,new].txt' >&2
+ echo 'Each input file should be from:' >&2
+ echo ' go test -test.run=NONE -test.bench=. > [old,new].txt' >&2
+ echo >&2
echo 'Benchcmp compares the first and last for each benchmark.' >&2
+ echo >&2
+ echo 'If -test.benchmem=true is added to the "go test" command' >&2
+ echo 'benchcmp will also compare memory allocations.' >&2
exit 2
esac
@@ -27,10 +32,21 @@ $1 ~ /Benchmark/ && $4 == "ns/op" {
new[$1] = $3
if($6 == "MB/s")
newmb[$1] = $5
+\
+\ # allocs/op might be at $8 or $10 depending on if
+\ # SetBytes was used or not.
+\ if($8 == "allocs/op")
+\ newalloc[$1] = $7
+\ if($10 == "allocs/op")
+\ newalloc[$1] = $9
} else {
old[$1] = $3
-\ if($6 = "MB/s")
+\ if($6 == "MB/s")
oldmb[$1] = $5
+\ if($8 == "allocs/op")
+\ oldalloc[$1] = $7
+\ if($10 == "allocs/op")
+\ oldalloc[$1] = $9
}
}
@@ -62,5 +78,21 @@ END {
sprintf("%.2f", newmb[what]),
sprintf("%.2f", newmb[what]/oldmb[what]))
}
+\
+\ # print allocs
+\tanyalloc = 0
+\tfor(i=0; i<n; i++) {
+\t\twhat = name[i]\
+\t\tif(!(what in newalloc))\
+\t\t\tcontinue
+\t\tif(anyalloc++ == 0)\
+\t\t\tprintf("\n%-*s %12s %12s %7s\n", len, "benchmark", "old allocs", "new allocs", "delta")
+\t\tif(oldalloc[what] == 0)\
+\t\t\tdelta="n/a"\
+\t\telse
+\t\t\tdelta=sprintf("%.2f", 100*newalloc[what]/oldalloc[what]-100)
+\t\tprintf("%-*s %12d %12d %6s%%\\n", len, what,\
+\t\t\toldalloc[what], newalloc[what], delta)
+\t}
}\
' "$@"
コアとなるコードの解説
-
ヘルプメッセージの更新:
- echo 'Each input file should be go test -test.run=NONE -test.bench=. > [old,new].txt' >&2 + echo 'Each input file should be from:' >&2 + echo ' go test -test.run=NONE -test.bench=. > [old,new].txt' >&2 + echo >&2 + echo 'If -test.benchmem=true is added to the "go test" command' >&2 + echo 'benchcmp will also compare memory allocations.' >&2
benchcmp
の使用方法を示すヘルプメッセージが更新され、-test.benchmem=true
をgo test
コマンドに追加することでメモリ割り当ての比較も可能になることが明示されました。これはユーザーにとって非常に重要な情報です。 -
メモリ割り当て統計の抽出ロジックの追加:
awk
スクリプトのBenchmark
行を処理する部分に、allocs/op
の値を抽出するロジックが追加されました。# allocs/op might be at $8 or $10 depending on if # SetBytes was used or not. if($8 == "allocs/op") newalloc[$1] = $7 if($10 == "allocs/op") newalloc[$1] = $9
同様のロジックが
else
ブロック(old
データ用)にも追加されています。newalloc[$1] = $7
およびoldalloc[$1] = $7
:$8
フィールドが"allocs/op"
である場合、その前のフィールド$7
がallocs/op
の値であると判断し、newalloc
またはoldalloc
配列に格納します。newalloc[$1] = $9
およびoldalloc[$1] = $9
:$10
フィールドが"allocs/op"
である場合、その前のフィールド$9
がallocs/op
の値であると判断し、同様に格納します。 この二重チェックにより、b.SetBytes()
の使用有無による出力フォーマットの差異に対応し、allocs/op
の値を確実に取得できるようになります。
-
メモリ割り当て統計の出力ロジックの追加:
awk
スクリプトのEND
ブロック(すべての入力行の処理が完了した後)に、メモリ割り当て統計を出力する新しいセクションが追加されました。# print allocs anyalloc = 0 for(i=0; i<n; i++) { what = name[i] if(!(what in newalloc)) continue if(anyalloc++ == 0) printf("\n%-*s %12s %12s %7s\n", len, "benchmark", "old allocs", "new allocs", "delta") if(oldalloc[what] == 0) delta="n/a" else delta=sprintf("%.2f", 100*newalloc[what]/oldalloc[what]-100) printf("%-*s %12d %12d %6s%%\\n", len, what, oldalloc[what], newalloc[what], delta) }
anyalloc = 0
: メモリ割り当て統計が一つでも存在するかを追跡するためのフラグ。for(i=0; i<n; i++)
: 処理されたすべてのベンチマーク名をループします。if(!(what in newalloc)) continue
:newalloc
に対応するベンチマークのデータがない場合はスキップします。if(anyalloc++ == 0) printf(...)
: 最初のメモリ割り当て統計を出力する際に、ヘッダー行(benchmark
,old allocs
,new allocs
,delta
)を一度だけ出力します。if(oldalloc[what] == 0) delta="n/a"
: 以前のallocs/op
が0の場合、デルタは計算できないため"n/a"
と表示します。else delta=sprintf("%.2f", 100*newalloc[what]/oldalloc[what]-100)
: それ以外の場合、新しいallocs/op
と古いallocs/op
の変化率をパーセンテージで計算し、小数点以下2桁で表示します。printf(...)
: 整形された形式で、ベンチマーク名、古いallocs/op
、新しいallocs/op
、そしてデルタ(変化率)を出力します。
これらの変更により、benchcmp
は単なる時間ベースのパフォーマンス比較ツールから、メモリ割り当ての効率性も評価できる、より包括的なベンチマーク比較ツールへと進化しました。
関連リンク
- Go CL (Code Review): https://golang.org/cl/6587069
参考にした情報源リンク
- Go言語のベンチマークと
-benchmem
フラグに関するドキュメントやチュートリアル- https://pkg.go.dev/testing
- https://go.dev/blog/go1.2-benchmarks (Go 1.2 でのベンチマーク改善に関するブログ記事、
B/op
とallocs/op
の導入について触れられている可能性)
awk
コマンドのフィールド処理と正規表現に関するドキュメントbenchcmp
ツールの機能と使用方法に関する情報 (現在はbenchstat
が推奨されているが、当時のbenchcmp
の挙動を理解するために参照)- https://pkg.go.dev/golang.org/x/perf/cmd/benchstat (
benchcmp
の後継ツールであるbenchstat
のドキュメントだが、benchcmp
の機能の文脈で役立つ情報が含まれる) - Web検索結果から得られた
benchcmp
の使用例や説明。
- https://pkg.go.dev/golang.org/x/perf/cmd/benchstat (