[インデックス 14729] ファイルの概要
このコミットは、Go言語のベンチマーク比較ツールである misc/benchcmp
に、バイトアロケーション(メモリ割り当て)統計を表示する機能を追加するものです。これにより、ベンチマーク結果の比較において、実行時間だけでなくメモリ使用量の変化も詳細に追跡できるようになります。
コミット
commit e09f1e7a46c0bd0f2160fa0d71d4b89104c6fe3b
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sat Dec 22 14:51:16 2012 -0500
misc/benchcmp: show byte allocation statistics
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6971048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e09f1e7a46c0bd0f2160fa0d71d4b89104c6fe3b
元コミット内容
misc/benchcmp: show byte allocation statistics
変更の背景
Go言語のベンチマークツール (go test -bench
) は、実行時間だけでなく、B/op
(bytes allocated per operation: 1操作あたりの割り当てバイト数) や allocs/op
(allocations per operation: 1操作あたりの割り当て回数) といったメモリ割り当てに関する統計も出力できます。しかし、misc/benchcmp
ツールは、これらのメモリ割り当て統計を比較する機能を持っていませんでした。
このコミット以前の benchcmp
は、主にベンチマークの実行時間 (ns/op
や MB/s
) の比較に特化していました。しかし、パフォーマンス改善の取り組みにおいては、実行時間の短縮だけでなく、メモリ使用量の削減も重要な指標となります。特に、ガベージコレクションの負荷を軽減するためには、不要なメモリ割り当てを減らすことが不可欠です。
この変更は、開発者がコード変更によるメモリ割り当ての増減を容易に把握し、メモリ効率の改善に役立てることを目的としています。これにより、benchcmp
はより包括的なベンチマーク比較ツールとなり、Goプログラムの全体的なパフォーマンス最適化に貢献します。
前提知識の解説
Go言語のベンチマーク
Go言語には、標準でベンチマーク機能が組み込まれています。go test
コマンドに -bench
フラグを付けることで、ベンチマーク関数を実行できます。ベンチマーク関数は BenchmarkXxx(*testing.B)
という形式で定義され、b.N
回のループでテスト対象のコードを実行し、その平均実行時間などを計測します。
go test -benchmem
go test
ココマンドに -benchmem
フラグを追加すると、ベンチマーク結果にメモリ割り当てに関する統計情報が追加されます。具体的には以下の2つの指標が出力されます。
B/op
(bytes allocated per operation): 1回の操作あたりに割り当てられたバイト数。この値が小さいほど、メモリ効率が良いことを示します。allocs/op
(allocations per operation): 1回の操作あたりに行われたメモリ割り当ての回数。この値が小さいほど、ガベージコレクションの頻度が減り、パフォーマンスが向上する可能性があります。
これらの情報は、コード変更がメモリ使用量にどのような影響を与えたかを評価する上で非常に重要です。
misc/benchcmp
ツール
misc/benchcmp
は、Go言語の公式リポジトリの misc
ディレクトリに存在していたユーティリティスクリプトで、2つのベンチマーク結果ファイルを比較し、その差分を分かりやすく表示するためのツールです。これは主に awk
スクリプトとして実装されており、ベンチマーク結果のテキスト出力を解析して比較結果を整形します。
注意点: 現在では benchcmp
は非推奨となっており、より高機能で統計的に厳密な比較が可能な golang.org/x/perf/cmd/benchstat
が推奨されています。しかし、このコミットが作成された2012年当時は benchcmp
が主要な比較ツールでした。
awk
awk
は、テキストファイルからデータを抽出・加工するための強力なプログラミング言語です。パターンとアクションのペアで構成され、入力行がパターンにマッチした場合に指定されたアクションを実行します。benchcmp
は awk
を利用して、ベンチマーク結果の各行を解析し、必要な数値データを抽出していました。
技術的詳細
このコミットの主要な変更は、misc/benchcmp
スクリプト(awk
で書かれている)が、ベンチマーク結果からバイトアロケーション統計 (B/op
) を適切に抽出し、比較結果として表示できるようにすることです。
go test -benchmem
の出力形式は、ベンチマークの種類(SetBytes
を使用するかどうかなど)によって B/op
や allocs/op
のフィールドの位置が異なる場合があります。具体的には、allocs/op
が $8
または $10
の位置に出現する可能性があります。このコミットでは、B/op
が allocs/op
の直前のフィールド($5
または $7
)に位置するという規則を利用して、B/op
の値を正確に抽出するように awk
スクリプトを修正しています。
変更前は allocs/op
の値のみを newalloc
および oldalloc
配列に格納していましたが、変更後は B/op
の値も newbytes
および oldbytes
配列に格納するように拡張されています。
スクリプトの後半では、END
ブロック内で、既存の allocs/op
の比較出力に加えて、新しく B/op
の比較結果を出力するロジックが追加されています。これにより、ベンチマーク名、変更前のバイト数、変更後のバイト数、そしてその変化率(パーセンテージ)が整形されて表示されます。変化率の計算では、変更前のバイト数が0の場合に「n/a」と表示するなどの配慮もされています。
コアとなるコードの変更箇所
--- a/misc/benchcmp
+++ b/misc/benchcmp
@@ -35,18 +35,28 @@ $1 ~ /Benchmark/ && $4 == "ns/op" {\
# allocs/op might be at $8 or $10 depending on if
# SetBytes was used or not.\
- if($8 == "allocs/op")
+ # B/op might be at $6 or $8, it should be immediately
+ # followed by allocs/op
+ if($8 == "allocs/op") {
+ newbytes[$1] = $5
newalloc[$1] = $7
- if($10 == "allocs/op")
+ }
+ if($10 == "allocs/op") {
+ newbytes[$1] = $7
newalloc[$1] = $9
+ }
} else {
old[$1] = $3
if($6 == "MB/s")
oldmb[$1] = $5
- if($8 == "allocs/op")
+ if($8 == "allocs/op") {
+ oldbytes[$1] = $5
oldalloc[$1] = $7
- if($10 == "allocs/op")
+ }
+ if($10 == "allocs/op") {
+ oldbytes[$1] = $7
oldalloc[$1] = $9
+ }
}\
}\
@@ -94,5 +104,21 @@ END {\
printf("%-*s %12d %12d %6s%%\\n", len, what,\
oldalloc[what], newalloc[what], delta)\
}\
+\
+\t# print alloc bytes\
+\tanybytes = 0\
+\tfor(i=0; i<n; i++) {\
+\t\twhat = name[i]\
+\t\tif(!(what in newbytes))\
+\t\t\tcontinue\
+\t\tif(anybytes++ == 0)\
+\t\t\tprintf("\n%-*s %12s %12s %7s\\n", len, "benchmark", "old bytes", "new bytes", "delta")\
+\t\tif(oldbytes[what] == 0)\
+\t\t\tdelta="n/a"\
+\t\telse\
+\t\t\tdelta=sprintf("%.2f", 100*newbytes[what]/oldbytes[what]-100)\
+\t\tprintf("%-*s %12d %12d %6s%%\\n", len, what,\
+\t\toldbytes[what], newbytes[what], delta)\
+\t}\
}\
' "$@"\
コアとなるコードの解説
このコミットは、misc/benchcmp
スクリプト内の awk
ロジックを主に変更しています。
-
ベンチマーク結果の解析部分の変更:
$1 ~ /Benchmark/ && $4 == "ns/op"
のブロックは、ベンチマーク結果の行を処理する部分です。- 以前は
allocs/op
の値 ($7
または$9
) をnewalloc
およびoldalloc
配列に格納していました。 - 変更後、
if($8 == "allocs/op")
およびif($10 == "allocs/op")
の条件ブロック内で、allocs/op
の直前のフィールド ($5
または$7
) をB/op
の値として認識し、newbytes[$1] = $5
やoldbytes[$1] = $5
のようにnewbytes
およびoldbytes
配列に格納するようにしました。これにより、バイトアロケーションの数値データがベンチマーク名 ($1
) に紐付けられて保持されます。
-
バイトアロケーション統計の出力部分の追加:
END
ブロックは、すべての入力行の処理が完了した後に実行される部分です。- 既存の
allocs/op
の比較出力 (# print allocs
) の後に、新しく# print alloc bytes
というコメントで始まるブロックが追加されました。 - このブロックでは、
for(i=0; i<n; i++)
ループを使って、処理された各ベンチマーク (name
配列に格納されている) について反復処理を行います。 if(!(what in newbytes))
は、そのベンチマークにバイトアロケーションデータが存在しない場合はスキップします。if(anybytes++ == 0)
は、バイトアロケーションの出力ヘッダーを一度だけ表示するための条件です。ヘッダーには「benchmark」「old bytes」「new bytes」「delta」が含まれます。if(oldbytes[what] == 0)
は、変更前のバイト数が0の場合に、変化率を「n/a」と表示するための処理です。これは、0除算エラーを避けるためと、意味のある変化率を計算できないためです。else delta=sprintf("%.2f", 100*newbytes[what]/oldbytes[what]-100)
は、変更前後のバイト数に基づいて変化率をパーセンテージで計算し、小数点以下2桁でフォーマットします。- 最後に
printf("%-*s %12d %12d %6s%%\\n", len, what, oldbytes[what], newbytes[what], delta)
を使用して、ベンチマーク名、変更前後のバイト数、および変化率を整形して標準出力に表示します。
これらの変更により、benchcmp
はメモリ割り当ての効率に関する重要な洞察を提供できるようになり、Goプログラムのパフォーマンスチューニングにおいてより有用なツールとなりました。
関連リンク
- Go CL 6971048: https://golang.org/cl/6971048
参考にした情報源リンク
benchcmp
の使用方法に関する情報: https://go.dev/doc/articles/benchcmpbenchstat
の紹介とbenchcmp
の非推奨化に関する情報: https://github.com/golang/tools/tree/master/cmd/benchcmp- Goベンチマークとメモリ割り当ての計測に関する情報: https://betterstack.com/community/guides/logging/go-benchmarking/