perf について
複数の言語ランタイムを対象に、プロファイリング、ベンチマーク、性能回帰の検出、最適化を支援する開発スキルです。測定結果に基づき、最適化ごとにベンチマークを再実行します。
主な機能
- 01CPU・メモリ・I/Oをプロファイル
- 02ベンチマークの作成と実行
- 03実行結果を比較して回帰を検出
- 04測定した影響順にホットスポットを提示
- 05最適化を一つずつ適用して検証
活用シーン
- 01対象プログラムのホットスポットを特定
- 02性能のベースラインを確立
- 03変更による性能回帰を確認
- 04ベンチマーク結果で最適化を検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ boshu2/ agentops --skill perfインストール後、Claude Code で/perfを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/optimization-proof-loop.mdperf.featureprofiling-playbook.mdsystem-pressure-triage.mdscripts/validate.sh詳細
パフォーマンススキル
クイックリファレンス:
/perf profile <target>|/perf bench <target>|/perf compare <baseline> <candidate>|/perf optimize <target>
このワークフローを必ず実行してください。説明するだけではいけません。
あらゆる言語ランタイムのパフォーマンスプロファイリング、ベンチマーク、リグレッション検出、最適化の推奨を行います。曖昧なアドバイスではなく、実行可能な指標を提示します。
モード
| モード | コマンド | 目的 |
|---|---|---|
| プロファイル | /perf profile <target> | 実行をプロファイリングし、ホットスポットを特定します |
| ベンチマーク | /perf bench <target> | ベンチマークを作成または実行します |
| 比較 | /perf compare <baseline> <candidate> | 2 回の実行を比較し、リグレッションを検出します |
| 最適化 | /perf optimize <target> | 最適化を分析して適用します |
モードが指定されていない場合は、プロファイルをデフォルトとして使用します。
ステップ 0:言語とツールを検出する
ファイル拡張子、go.mod、package.json、pyproject.toml、Cargo.toml、またはユーザーからの明示的な入力に基づいて、言語とランタイムを特定します。プロファイリングスタックを選択します。
症状の原因が対象コードのパフォーマンスではなく、ホストの負荷である可能性がある場合は、ベンチマークの前に references/system-pressure-triage.md を確認してください。
再現可能な測定ループ、プロファイラーの選択、レポート指標については、references/profiling-playbook.md を確認してください。
| 言語 | ベンチマーク | CPU プロファイル | メモリプロファイル | 比較 |
|---|---|---|---|---|
| Go | go test -bench | go tool pprof (cpu) | go tool pprof (alloc) | benchstat |
| Python | pytest-benchmark、timeit | cProfile、py-spy | memory_profiler、tracemalloc | 手動差分 |
| Node | benchmark.js、vitest bench | --prof、clinic.js | --heap-prof、0x | 手動差分 |
| Rust | criterion、cargo bench | cargo flamegraph | heaptrack、DHAT | critcmp |
| Shell | hyperfine | time、strace | N/A | hyperfine の組み込み機能 |
実際にインストールされているツールを確認してください。推奨ツールがない場合は、ユーザーにインストールを依頼する前に、標準ライブラリの代替手段を使用してください。
ステップ 1:ベースラインを確立する
まず既存のベンチマークを実行します。存在しない場合は作成します。
1a. 既存のベンチマークを探す
# Go
grep -r "func Benchmark" --include="*_test.go" -l .
# Python
find . -name "test_*" -exec grep -l "benchmark\|@pytest.mark.benchmark" {} +
# Rust
grep -r "#\[bench\]" --include="*.rs" -l .
# Node
find . -name "*.bench.*" -o -name "*.benchmark.*"
1b. ベンチマークを実行または作成する
対象のベンチマークが存在する場合は実行し、出力を記録します。存在しない場合は、対象の関数またはモジュールを対象とするベンチマークを作成します。
ベンチマークの要件:
- 実時間(ops/sec または ns/op)を測定します
- メモリ割り当て(bytes/op、allocs/op)を測定します
- 統計的に安定するだけの回数を実行します(Go:
-benchtime=3s -count=5) - 該当する場合はレイテンシのパーセンタイルを記録します:p50、p95、p99
生のベースライン出力を .agents/perf/baseline-YYYY-MM-DD.txt に保存します。
1c. Go ベンチマークのテンプレート
func BenchmarkTargetFunction(b *testing.B) {
// Setup outside the loop
input := prepareInput()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
TargetFunction(input)
}
}
1d. Python ベンチマークのテンプレート
import pytest
@pytest.mark.benchmark(group="target")
def test_target_benchmark(benchmark):
input_data = prepare_input()
result = benchmark(target_function, input_data)
assert result is not None
ステップ 2:プロファイリングしてホットスポットを特定する
CPU プロファイリング
最も多くの CPU 時間を消費している関数を見つけます。
Go:
go test -bench=BenchmarkTarget -cpuprofile=cpu.prof ./...
go tool pprof -top cpu.prof
go tool pprof -text -cum cpu.prof # cumulative view
Python:
python -m cProfile -s cumulative target_script.py
# Or for running processes:
py-spy top --pid <PID>
py-spy record -o profile.svg --pid <PID>
メモリプロファイリング
メモリ割り当てのホットスポットと、メモリリークの可能性を見つけます。
Go:
go test -bench=BenchmarkTarget -memprofile=mem.prof ./...
go tool pprof -top -alloc_space mem.prof
Python:
python -m memory_profiler target_script.py
# Or with tracemalloc in code:
# tracemalloc.start(); ...; snapshot = tracemalloc.take_snapshot()
I/O プロファイリング
ホットパス内のブロッキング処理を特定します。
- ループ内の同期的なファイル I/O、ネットワーク呼び出し、データベースクエリを確認します。
- コネクションプーリングの欠如、バッファリングされていない書き込み、並行処理できるシリアルな HTTP 呼び出しを探します。
ホットスポットの概要
プロファイリング後、優先順位を付けたリストを作成します。
HOTSPOTS (by cumulative CPU time):
1. pkg/engine.Process 42.3% (1.2s) — main processing loop
2. pkg/engine.parseRecord 28.1% (0.8s) — record deserialization
3. pkg/io.ReadBatch 15.7% (0.45s) — disk reads
ステップ 3:分析して推奨事項を提示する
各所見を、推定される影響度に基づいて分類します。
| 影響度 | 基準 | 対応 |
|---|---|---|
| 高 | 全体の時間の 20% 超、またはメモリ割り当ての 50% 超 | すぐに修正します |
| 中 | 全体の時間の 5~20%、または顕著なメモリ割り当ての無駄 | このセッション中に修正します |
| 低 | 全体の時間の 5% 未満、軽微な非効率性 | 後で対応するために記録します |
よくあるアンチパターン
プロファイリングしたコードに、次のよく知られたパフォーマンス低下の原因がないか確認します。
- 不要な割り当て — ホットループ内での割り当て、ループ内での文字列連結(
strings.Builder/[]byte/io.StringWriterを使用します) - N+1 クエリ — バッチクエリではなく、アイテムごとにデータベースを呼び出すこと
- キャッシュの欠如 — 変更頻度の低い高コストな結果を再計算すること
- ホットパスでのブロッキング I/O — 非同期 I/O やバッファリング I/O で対応できる箇所で、同期的なネットワーク/ディスク呼び出しを行うこと
- 過剰なコピー — 大きな構造体を値渡しすること、スライスをスライス化せずコピーすること
- 最適でないデータ構造 — マップ検索で済む箇所で線形検索すること、事前割り当てせずスライスを無制限に拡張すること
- ロック競合 — I/O や長時間の計算中もミューテックスを保持すること
- ループ内での正規表現コンパイル — 一度だけコンパイルし、コンパイル済みパターンを再利用します
- ホットパスでのリフレクション — コード生成または型スイッチに置き換えます
- バッファなしチャネル — Go でゴルーチンのスケジューリングオーバーヘッドを引き起こすこと
各指摘について、次の内容を示します。
- 内容:該当するコードの位置とパターン
- 理由:プロファイリングの数値を使って、パフォーマンスに悪影響を及ぼす仕組みを説明します
- 修正:具体的なコード変更の提案
ステップ 4:最適化(最適化モードのみ)
重要なルール:一度に最適化するのは 1 つだけです。
工数の大きい最適化作業では、コードを変更する前に references/optimization-proof-loop.md を読み込みます。ここでは、同型の書き換え、ベンチマークの差分、変更を維持するか元に戻すかの判断に関する検証要件を定義しています。
最適化ごとに、次の手順に従います。
- 説明:変更を加える前に、その内容を説明します
- 適用:変更を 1 つだけ適用します
- 再実行:ベンチマークスイートを再実行します
- 比較:
benchstat(Go)または手動差分を使って、ベースラインと結果を比較します - 維持または差し戻し:指標を測定可能な形で改善する変更だけを維持します
- コミット:次の形式のメッセージを使用します:
perf(<scope>): <description> (+X% throughput)またはperf(<scope>): <description> (-X% latency)
受け入れ基準
- 改善は統計的に有意である必要があります(
benchstatでは p < 0.05、または手動比較で 5% を超える一貫した変化) - 正確性のリグレッションがないこと — 既存のテストがすべて引き続き通る必要があります
- わずかな改善(2% 未満)のために可読性を損なわないこと(難読化したコードに見合う効果がありません)
最適化の順序
期待される効果が大きいものから、次の順に最適化を適用します。
- アルゴリズムの改善(O(n^2) から O(n log n) など)
- 割り当ての削減(事前割り当て、プール、バッファの再利用)
- I/O のバッチ処理とバッファリング
- キャッシュとメモ化
- 並行性の改善(独立した処理を並列化)
- マイクロ最適化(プロファイリングで効果が確認された場合のみ)
ステップ 5:レポートを出力
レポートを .agents/perf/YYYY-MM-DD-perf-<target>.md に書き込みます。
レポートテンプレート
# Performance Report: <target>
Date: YYYY-MM-DD
Mode: <profile|bench|compare|optimize>
Language: <detected>
## Summary
<1-2 sentence summary of findings>
## Baseline Metrics
| Metric | Value |
|--------|-------|
| ops/sec | ... |
| ns/op | ... |
| B/op | ... |
| allocs/op | ... |
| p50 latency | ... |
| p95 latency | ... |
| p99 latency | ... |
## Hotspots
<ranked list from Step 2>
## Findings
<classified findings from Step 3>
## Optimizations Applied (if optimize mode)
| Change | Before | After | Improvement |
|--------|--------|-------|-------------|
| ... | ... | ... | +X% |
## After Metrics (if optimize mode)
<same table as baseline, with new values>
## Recommendations
<remaining opportunities not addressed in this session>
比較モードの詳細
/perf compare <baseline> <candidate> を実行する場合:
- 両方のバージョンのベンチマークを探すか、再実行します
- 言語固有の比較ツールを使用します。
- Go:
benchstat baseline.txt candidate.txt - Rust:
critcmp baseline candidate - その他:パーセンテージ差を含む左右比較の表
- Go:
- リグレッション(5% を超える速度低下、または 10% を超える割り当て増加)に REGRESSION のフラグを付けます
- 改善(5% を超える高速化、または 10% を超える割り当て削減)に IMPROVEMENT のフラグを付けます
- 統計的に有意でない変化に NOISE のフラグを付けます
次のような要約テーブルを出力します。
COMPARISON: baseline vs candidate
| Benchmark | Baseline | Candidate | Delta | Verdict |
|-----------|----------|-----------|-------|---------|
| BenchmarkProcess | 1.2ms | 0.9ms | -25% | IMPROVEMENT |
| BenchmarkParse | 450ns | 480ns | +6.7% | REGRESSION |
| BenchmarkIO | 3.1ms | 3.0ms | -3.2% | NOISE |
エッジケース
- ベンチマークがなく、明確な対象もない場合:まず
/refactorを実行してホットパスを特定し、その後、それらをベンチマークします。 - ベンチマークが不安定な場合:反復回数を増やし、単一コアに固定し(
GOMAXPROCS=1)、競合するプロセスを終了します。 - プロファイリングツールをインストールできない場合:実時間の計測には
timeを使い、割り当て数の計測には手動計測を使用します。 - 対象が CLI コマンドの場合:言語を問わず、実時間のベンチマークに
hyperfineを使用します。
関連項目
- refactor — 複雑度の高いコードを見つけて対象にします(複雑さを吸収)
- standards — 言語固有の最適化パターン
- validate — 最適化したコードの品質を検証します(vibe を吸収)
参考ドキュメント
-
references/perf.feature — 実行可能な仕様:ホットスポットのプロファイリングとメトリクスの計測、ベンチマーク、リグレッション比較、最適化(soc-qk4b)