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

[インデックス 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.txtnew.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)を抽出し、比較表示できるようにすることです。

benchcmpawk スクリプトを使用して 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 の値が存在すればそれを取得するロジックが追加されました。

具体的には、newallocoldalloc という連想配列(awk の配列)が導入され、ベンチマーク名($1)をキーとして、それぞれのベンチマークの allocs/op の値を格納します。

最終的な出力フェーズでは、これらの newallocoldalloc の値を使用して、ns/opMB/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}
 }\
 ' "$@"

コアとなるコードの解説

  1. ヘルプメッセージの更新:

    -	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=truego test コマンドに追加することでメモリ割り当ての比較も可能になることが明示されました。これはユーザーにとって非常に重要な情報です。

  2. メモリ割り当て統計の抽出ロジックの追加: 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" である場合、その前のフィールド $7allocs/op の値であると判断し、newalloc または oldalloc 配列に格納します。
    • newalloc[$1] = $9 および oldalloc[$1] = $9: $10 フィールドが "allocs/op" である場合、その前のフィールド $9allocs/op の値であると判断し、同様に格納します。 この二重チェックにより、b.SetBytes() の使用有無による出力フォーマットの差異に対応し、allocs/op の値を確実に取得できるようになります。
  3. メモリ割り当て統計の出力ロジックの追加: 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言語のベンチマークと -benchmem フラグに関するドキュメントやチュートリアル
  • awk コマンドのフィールド処理と正規表現に関するドキュメント
  • benchcmp ツールの機能と使用方法に関する情報 (現在は benchstat が推奨されているが、当時の benchcmp の挙動を理解するために参照)
    • https://pkg.go.dev/golang.org/x/perf/cmd/benchstat (benchcmp の後継ツールである benchstat のドキュメントだが、benchcmp の機能の文脈で役立つ情報が含まれる)
    • Web検索結果から得られた benchcmp の使用例や説明。