golang-benchmark について
信頼できるベンチマーク結果を必要とする Go 開発者向けの性能測定スキルです。ベンチマーク作成、プロファイリング、統計比較、CI での回帰検出を扱います。
主な機能
- 01ベンチマーク作成と実行の指針
- 02割り当て量とカスタム指標の測定
- 03CPU・メモリ・トレースのプロファイリング
- 04benchstat による統計比較
- 05CI 性能回帰検出のリファレンス
活用シーン
- 01最適化前の Go コードパス測定
- 02複数回のベンチマーク結果の比較
- 03CPU やメモリのコスト調査
- 04CI での性能回帰チェック
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-benchmarkインストール後、Claude Code で/golang-benchmarkを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/benchstat.mdci-regression.mdcompiler-analysis.mdinvestigation-session.mdpprof.mdprometheus-go-metrics.mdtools.mdtrace.md詳細
ペルソナ: Go のパフォーマンス測定エンジニアです。ベンチマークを一度実行しただけで結論を出すことはありません。最適化の判断を行う前提として、統計的な厳密さと管理された条件が必要です。
思考モード: ベンチマーク分析、プロファイルの解釈、パフォーマンス比較のタスクでは、できる限り徹底的に推論してください。深く考えることで、プロファイリングデータの誤解を防ぎ、統計的に妥当な結論を導けます。Claude Code では、ultrathink を使って拡張思考を明示的に開始してください。
依存関係:
- benchstat:
go install golang.org/x/perf/cmd/benchstat@latest
Go ベンチマークとパフォーマンス測定
測定なしにパフォーマンスの改善はありません。測定できれば、改善できます。
このスキルでは、ベンチマークの作成、実行、結果のプロファイリング、統計的に厳密な変更前後の比較、CI でのリグレッション追跡まで、測定ワークフロー全体を扱います。測定後に適用する最適化パターンについては、→ samber/cc-skills-golang@golang-performance スキルを参照してください。実行中のサービスでの pprof の設定については、→ samber/cc-skills-golang@golang-troubleshooting スキルを参照してください。
ベンチマークの作成
ファイルと順序の規約
ベンチマーク関数は、個々の関数ではなく、対象となるソースファイルにちなんだ名前の _bench_test.go ファイルに置きます。たとえば、parser.go の場合は parser_bench_test.go とし、関数ごとに benchmarkparse_test.go を分けて作るのではなく、BenchmarkParse、BenchmarkEncode などを含めます。
- ベンチマークを専用ファイルに置くことで(
parser_test.goに混在させるのではなく)、go test -bench=. ./pkg/parserの出力に無関係なTest*のノイズが混ざらなくなります。 - 測定用のサイズに合わせたフィクスチャ(大きな入力や長時間保持するセットアップ)と、正確性検証用のフィクスチャを分離できます。この2つが同じ形になることはほとんどありません。
- このファイルも Go の「ソースファイルごとにテストファイルを1つ作る」規約に従います(→
samber/cc-skills-golang@golang-testingスキルを参照)。ただし、用途を限定する_benchサフィックスを付けます。
parser_bench_test.go 内の Benchmark* 関数は、測定対象となる parser.go の関数やメソッドの順序に合わせて並べます。2つのファイルを上から見比べたとき、BenchmarkParse が Parse と同じ相対位置にあるようにします。
b.Loop()(Go 1.24 以降)— 推奨
Go 1.24 以降では、新しいベンチマークに b.Loop() を使うことを推奨します。ループ本体だけを計測し、関数の引数や戻り値を有効な状態に保つため、デッドコード除去に関するミスを減らせます。
func BenchmarkParse(b *testing.B) {
data := loadFixture("large.json") // setup — excluded from timing
for b.Loop() {
Parse(data) // compiler cannot eliminate this call
}
}
既存のベンチマークを維持する場合や Go <1.24 をサポートする場合は、従来の b.N ループを使い続けても問題ありません。ただし、間違いが起きやすく、セットアップで b.ResetTimer() が必要になったり、コンパイラーが処理を除去できる場合に結果をシンクへ渡す必要が生じたりします。Go 1.26 では、以前の b.Loop() のインライン化の制限が修正されました。Go 1.24~1.25 のベンチマークでも b.Loop() の利点は得られますが、Go 1.26 で実現するインライン化の最適化は得られない場合があります。
Go 1.27 のサイズ特化アロケーターの変更により、コードに変更がなくても、アロケーションが多いベンチマークのベースラインが変わります(80 バイト未満のアロケーションは高速化され、バイナリは大きくなります)。Go 1.26 から Go 1.27 へのツールチェーンの境界をまたぐ benchstat の比較は、コードではなくツールチェーンを測定しているものとして扱ってください。差分を信頼する前に、「変更前」のベンチマークを「変更後」と同じツールチェーンで再実行してください。
メモリの追跡
func BenchmarkAlloc(b *testing.B) {
b.ReportAllocs() // or run with -benchmem flag
var sink []byte
for b.Loop() {
sink = make([]byte, 1024)
}
_ = sink
}
b.ReportMetric() を使うと、スループットなどのカスタムメトリクスを追加できます。
b.ReportMetric(float64(totalBytes)/b.Elapsed().Seconds(), "bytes/s") // b.Elapsed() is only valid inside b.Loop()
サブベンチマークとテーブル駆動
func BenchmarkEncode(b *testing.B) {
for _, size := range []int{64, 256, 4096} {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
data := make([]byte, size)
for b.Loop() {
Encode(data)
}
})
}
}
ベンチマークの実行
go test -bench=BenchmarkEncode -benchmem -count=10 ./pkg/... | tee bench.txt
| フラグ | 目的 |
|---|---|
-bench=. | すべてのベンチマークを実行(正規表現フィルター) |
-benchmem | 割り当て量を報告(B/op、allocs/op) |
-count=10 | 統計的有意性を確保するため10回実行 |
-benchtime=3s | ベンチマークごとの最小時間(デフォルトは1s) |
-cpu=1,2,4 | 異なるGOMAXPROCS値で実行 |
-cpuprofile=cpu.prof | CPUプロファイルを書き込む |
-memprofile=mem.prof | メモリプロファイルを書き込む |
-trace=trace.out | 実行トレースを書き込む |
出力形式: BenchmarkEncode/size=64-8 5000000 230.5 ns/op 128 B/op 2 allocs/op — -8の接尾辞はGOMAXPROCS、ns/opは操作あたりの時間、B/opは操作あたりの割り当てバイト数、allocs/opは操作あたりのヒープ割り当て回数です。
最適化のバリエーションを並行して比較する
同じボトルネックに対して競合する最適化の仮説が複数ある場合は、それぞれのバリエーションを個別のサブエージェントに割り当て、独立したワークツリーで実装します。これにより、共有作業ツリーでコード変更が衝突することを防げます。
ベンチマークは並行ではなく、順番に実行してください。 ベンチマークを並行して実行すると、CPUを共有するため、ノイジーネイバー効果によってns/opが汚染され、-countとbenchstatで取り除こうとしている統計的ノイズが再び発生します。実装の並行作業は安全です(ワークツリーが分離され、ファイルの競合がありません)が、測定の並行作業は安全ではありません(ハードウェアを共有し、実際に競合します)。各バリエーションのベンチマークは、メインツリーに戻って1つずつ実行するか、ワークツリーごとに順番に実行してください。
すべてのバリエーションのbenchstat出力を同じベースラインレポートと比較し、最良のものを残して、残りのワークツリーを削除してください。
コミットでの結果の記録
変更に測定可能なパフォーマンスへの影響がある場合は、コミット本文にbenchstatの出力を貼り付けてください。これにより、最適化を行った理由が記録され、後から読む人が変更を元に戻すのを防げます。また、レビュアーはベンチマークを再実行せずに主張を検証できます。
コミット形式:
perf(parser): reduce Parse allocations 50% with sync.Pool
Replace per-call []byte allocation with a pooled buffer.
goos: linux / goarch: amd64 / cpu: AMD Ryzen 9 5950X
│ old │ new │
│ sec/op │ sec/op vs base │
Parse-32 4.592µ ± 2% 3.041µ ± 1% -33.78% (p=0.000 n=10)
│ old │ new │
│ B/op │ B/op vs base │
Parse-32 1.024Ki ± 0% 0.512Ki ± 0% -50.00% (p=0.000 n=10)
│ old │ new │
│ allocs/op │ allocs/op vs base │
Parse-32 12.00 ± 0% 6.000 ± 0% -50.00% (p=0.000 n=10)
ルール:
- 変更の影響を直接受けるベンチマークのみを含め、無関係な行は削除してください
~が付いた結果(統計的有意性がない結果)は決して貼り付けないでください。改善を主張できません- 結果を再現できるように、ハードウェア情報の行(
goos/goarch/cpu)を含めてください - パフォーマンスのみの変更には、コミットタイプ
perf(scope):を使用してください
ベンチマークからのプロファイリング
HTTPサーバーは不要です。ベンチマーク実行から直接プロファイルを生成できます。
# CPU profile
go test -bench=BenchmarkParse -cpuprofile=cpu.prof ./pkg/parser
go tool pprof cpu.prof
# Memory profile (alloc_objects shows GC churn, inuse_space shows leaks)
go test -bench=BenchmarkParse -memprofile=mem.prof ./pkg/parser
go tool pprof -alloc_objects mem.prof
# Execution trace
go test -bench=BenchmarkParse -trace=trace.out ./pkg/parser
go tool trace trace.out
pprof CLIの完全なリファレンス(すべてのコマンド、非対話モード、プロファイルの解釈)については、pprofリファレンスを参照してください。実行トレースの解釈については、トレースリファレンスを参照してください。統計的な比較については、benchstatリファレンスを参照してください。
参考ファイル
-
pprof リファレンス — CPU、メモリ、ゴルーチンのプロファイルを対話形式または非対話形式で分析します。CLI コマンド一式、プロファイルの種類(CPU、alloc*objects、inuse_space)、ウェブ UI の操作方法、解釈のパターンを網羅しています。コード内で時間やメモリが「どこに」費やされているのかを詳しく調べるときに活用してください。
-
benchstat リファレンス — 厳密な信頼区間と p 値検定によって、ベンチマークの実行結果を統計的に比較します。出力の読み方、古いベンチマークの除外、視覚的に分かりやすくするための結果の交互表示、リグレッションの検出を網羅しています。運のよい実行結果ではなく、変更によって有意なパフォーマンス差が生じたことを証明したいときに活用してください。
-
トレース リファレンス — コードが「いつ」「なぜ」実行されるかを理解するための実行トレーサーです。ゴルーチンのスケジューリング、ガベージコレクションのフェーズ、ネットワークのブロッキング、カスタムのスパン注釈を可視化します。CPU が「どこで」使われているかを示す pprof だけでは不十分で、何が起きたかのタイムラインを確認したいときに活用してください。
-
診断ツール — 補助ツールのクイックリファレンスです。fieldalignment(構造体のパディングによる無駄)、GODEBUG(ランタイムのログ出力フラグ)、fgprof(フレームグラフのプロファイル)、race detector(並行処理のバグ検出)などを紹介します。特定の症状があり、絞り込んだ診断が必要なときに活用してください。よりシンプルなツールで疑問が解決できる場合は、pprof に頼る必要はありません。
-
コンパイラー分析 — コンパイラーの最適化に関する低レベルの知見を紹介します。エスケープ解析(値がヒープに移動するタイミング)、インライン化の判断(どの関数呼び出しが除去されるか)、SSA ダンプ(中間表現)、アセンブリ出力を扱います。ベンチマークで予想外のアロケーションが見つかったときや、コンパイラーが意図どおりに動作したか確認したいときに活用してください。
-
CI でのリグレッション検出 — CI パイプラインでパフォーマンスのリグレッションを自動的に判定します。3 つのツール(PR の簡易比較に使う benchdiff、しきい値に基づく厳格な判定に使う cob、長期的な傾向のダッシュボードに使う gobenchdata)、ノイズの多い隣接ジョブへの対策(クラウド CI のベンチマークが、負荷の低いマシンでも 5~10% 変動する理由)、ベンチマークの再現性を高めるセルフホストランナーの調整方法を網羅しています。プルリクエストによってコードベースの速度が気づかないうちに低下するのを防ぎたいときに活用してください。リグレッションを早期に検出すれば、パフォーマンス上の負債を抱えたままリリースするのを防げます。
-
調査セッション — Prometheus のランタイムメトリクス(ヒープサイズ、GC の頻度、ゴルーチン数)、メトリクスとコード変更を関連付ける PromQL クエリ、ランタイム設定フラグ(GC ログを有効にする GODEBUG 環境変数)、コストに関する警告(パフォーマンス上の負荷が限界に達している場合)を組み合わせた、本番環境のパフォーマンス問題のトラブルシューティングワークフローです。本番環境のベンチマーク結果は良好でも、実際のトラフィックでは挙動が異なるときに活用してください。
-
Prometheus Go メトリクスリファレンス —
prometheus/client_golangが Prometheus メトリクスとして実際に公開する Go ランタイムメトリクスを網羅しています。デフォルトの 30 個のメトリクス、オプションの 40 個以上のメトリクス(Go 1.17 以降)、プロセスメトリクス、よく使われる PromQL クエリを紹介します。runtime/metrics(Go 内部のデータ)と Prometheus メトリクス(/metricsからスクレイプするデータ)の違いも説明します。監視ダッシュボードの設定や、本番環境のアラート用 PromQL クエリの作成に活用してください。
相互参照
- → 測定後に適用する最適化パターン(「X がボトルネックなら Y を適用」)については、
samber/cc-skills-golang@golang-performanceスキルを参照してください - → 稼働中のサービスでの pprof の設定(有効化、安全確保、取得)、Delve デバッガー、GODEBUG フラグ、根本原因の分析手法については、
samber/cc-skills-golang@golang-troubleshootingスキルを参照してください - → 日常的な常時監視、継続的プロファイリング(Pyroscope)、分散トレーシング(OpenTelemetry)については、
samber/cc-skills-golang@golang-observabilityスキルを参照してください - → 一般的なテストの実践方法については、
samber/cc-skills-golang@golang-testingスキルを参照してください - → ベンチマーク結果を検証するため、本番環境で Prometheus のランタイムメトリクスをクエリする方法については、
samber/cc-skills@promql-cliスキルを参照してください