Claude Codeでcriterionの結果を読み、Rustの遅い関数を直す手順
criterionのchange行とp値からClaude Codeに誤差かどうかを判定させ、cargo flamegraphで遅い関数を絞って直し、同じベンチで再計測するまでの手順です。
Rustの関数を速くしたいとき、Claude Codeに「criterionの結果を見て直して」と頼むだけでは足りません。criterionは前回の実行と自動で比べて change: の行を出しますが、その行が「本当に速くなった」のか「ただの揺れ」なのかは、読み方を知らないと判定できないからです。
この記事では、criterionの出力から誤差かどうかを判定する基準、比較用のbaselineの取り方、cargo flamegraph で遅い関数を絞る方法、そして直した後に同じベンチで確かめ直す流れをまとめます。CodSpeed経由で計測する場合はCodSpeedプラグインの記事が別の入口です。ここでは cargo bench を直接使います。
criterionのchange行は何を言っているか
criterionはベンチを実行すると、target/criterion に統計情報を保存します。次に同じベンチを実行したとき、その保存データと今回の測定を比べて差を出します。以下は公式ドキュメントに載っている出力例です。
alloc time: [2.5094 ms 2.5306 ms 2.5553 ms]
change: [-38.292% -37.342% -36.524%] (p = 0.00 < 0.05)
Performance has improved.time: は1回あたりの時間の信頼区間で、左が下限、右が上限、中央が最良の推定値です。change: は前回との差の信頼区間と、偶然その差が出た確率(p値)を表します。判定の文言は、criterionが統計的に強い根拠があると見たときだけ「improved」か「regressed」になります。
読み分けに迷うのは次の3パターンです。公式の例をそのまま並べます。
| change行の例 | p値 | criterionの判定文言 | 読み方 |
|---|---|---|---|
[-38.292% -37.342% -36.524%] | p値0.00 < 0.05 | criterionの判定文言Performance has improved. | 読み方区間が0をまたがず、差は有意 |
[-1.8316% +0.9121% +3.4704%] | p値0.52 > 0.05 | criterionの判定文言No change in performance detected. | 読み方区間が0をまたぐ。差は検出されていない |
[-0.7479% +3.2888% +7.5451%] | p値0.04 > 0.05 | criterionの判定文言Change within noise threshold. | 読み方差はノイズ閾値内として扱われる |
3行目の例は、p値の表記が 0.04 > 0.05 となっていて、数字だけを見ると不等号が逆です。公式ドキュメントにこの形で載っているため、Claude Codeに渡すときもp値の数字だけで判定させず、末尾の判定文言を優先して読ませるほうが安全です。
ノイズ閾値は既定で±2%です。測定結果の区間がこの範囲に重なる場合は、ノイズとして有意でない扱いになります。決定論的なベンチでは小さな揺れも拾うため、この閾値があります。
誤差を疑うサイン: outlier・R^2・標準偏差
判定文言だけでなく、その下の統計行も渡す価値があります。
Found 8 outliers among 100 measurements (8.00%)
4 (4.00%) high mild
4 (4.00%) high severe
slope [2.5094 ms 2.5553 ms] R^2 [0.8660614 0.8640630]
mean [2.5142 ms 2.5557 ms] std. dev. [62.868 us 149.50 us]
median [2.5023 ms 2.5262 ms] med. abs. dev. [40.034 us 73.259 us]公式ドキュメントの説明に沿うと、見るべき点は3つです。
- outlierが多いと、結果がノイズを含むので慎重に見る。原因は他プロセスの負荷やスケジューリングなど
- R^2が低いと、イテレーションごとの仕事量がそろっていない可能性がある
- 標準偏差(std. dev.)が時間の値に比べて大きいと、ベンチがノイジー
さらに公式は注意書きで、バッテリー駆動と電源接続を切り替えただけで、コードを変えていないのに「改善」「悪化」の判定が出た例を挙げています。実行環境をそろえずに前後を比べると、Claude Codeがどれだけ正しく読んでも結論を誤ります。計測は静かなマシンで、同じ条件で取ります。
測定中の出力と、ノイズを減らす設定
cargo bench を実行すると、判定行の前に Warming up と Collecting 100 samples in estimated ... s の行が流れます。前者はウォームアップで、プロセッサやファイルシステムのキャッシュを温めるための反復です。ドキュメントによると期間は設定でき、既定は3秒です。後者の estimated は、ウォームアップ中に測った1回あたりの時間から割り出した所要時間の見積もりで、サンプル数も設定できます。
信頼区間の幅も設定で動きます。信頼水準を99%のように上げると区間は広がり、真の傾きについて分かる情報は減ります。90%に下げると区間は狭まりますが、その区間に真の値が入っている確からしさは下がります。ドキュメントは95%を「バランスの良い値」としています。区間はブートストラップ法(元データから再抽出を繰り返す推定)で作られ、再抽出の回数は既定で10万回です。
読み違いを避けるための切り替えは2つあります。
--verboseを付けると、cargo benchが省く詳細な出力まで出る--quickを付けると速く終わるが、統計的な保証は下がる。判定行の差が小さいときの根拠には向かない
--quick で出た「No change」を、そのまま差なしの証拠として依頼文に載せないほうが安全です。迷った場合は通常の設定で測り直します。
前後を比べられる形でベンチを取る
差を読ませるには、比較の基準が名前付きで残っている必要があります。criterionの --save-baseline と --baseline を使います。
# 変更前のコードで基準を保存
git switch main
cargo bench -- --save-baseline before
# 変更後のコードで測り、保存済みの基準と比べる
git switch perf-fix
cargo bench -- --baseline before--save-baseline <name> は名前付き基準と比べたうえで上書きし、--baseline <name> は上書きせずに比べます。指定した基準にベンチ結果が欠けているとエラーになります。ブランチ間をCIで比べるなど欠けを許したい場合は --baseline-lenient <name> を使います。
基準は名前を付けて複数残せます。--load-baseline <name> は、保存済みの基準を新しいデータセットとして読み込みます。再測定せずに複数の基準と見比べたいときに使います。
特定のベンチだけ回したいときは、cargo bench -- <正規表現> でベンチIDを絞れます。出力を人間より先にClaude Codeへ渡すなら、--color never で色コードを消し、--noplot でグラフ生成を省くと扱いやすくなります。
Claude Codeに判定を頼む依頼文
出力をファイルに落として渡す形にすると、再現しやすくなります。
cargo bench -- --baseline before --color never --noplot \
2>&1 | tee bench-after.txt依頼文は、判定基準を先に書きます。公式のベストプラクティスは、具体的に書いたプロンプトのほうが後の修正が減ると説明しています。
@bench-after.txt は cargo bench -- --baseline before の出力です。
各ベンチについて、次の順で判定して表にしてください。
1. change 行の区間が 0 をまたぐ、または判定文言が
"No change" / "within noise threshold" のものは「差なし」とする
2. outlier の割合、R^2、std. dev. が時間に対して大きいものは
「ノイズ大」と注記し、再計測を提案する
3. improved / regressed のものだけ、変化率の中央値を書く
コードの修正はまだしないでください。最後の1行が大事です。判定と修正を同じ依頼に混ぜると、「差なし」のベンチまで修正の対象に入りかねません。まず表だけ作らせ、人間が「このベンチは本当に遅い」と合意してから次へ進みます。
cargo flamegraphで遅い関数を絞る
ベンチが「遅い」と確定したら、どの関数が時間を食っているかを cargo flamegraph で見ます。cargo-flamegraphはLinuxでは perf、macOSでは xctrace に依存し、Windowsではblondieライブラリで動きます。
ベンチのフレームグラフには、事前に設定が要ります。READMEの指示は次のとおりです。
# Cargo.toml
[profile.bench]
debug = trueテスト・ユニットテスト・ベンチは、リリースモードの bench プロファイルを使います。debug = true を入れないと、最適化の影響で関数名や行の情報が荒くなります。
実行はREADMEの例にならい、最後に -- --bench を付けます。criterion 0.3で、テストモードではなくベンチモードで動かすために必要と書かれています。
cargo flamegraph --bench parse_bench -- --benchcriterionのプロファイリング節には、--profile-time <秒数> もあります。指定した秒数ほどベンチを繰り返し、criterion自身の分析コードをプロファイルに混ぜず、結果の保存もしません。前述の --bench と組み合わせて、cargo flamegraph --bench parse_bench -- --bench --profile-time 10 の形にすると、計測コードの影を減らせます。--bench と --profile-time はどちらもcriterionのドキュメントにあるオプションですが、この2つを組み合わせた例そのものは載っていません。
Linuxでよくつまずく点が2つあります。
- lld(Rust 1.90.0以降の既定)やmoldを使っていると、
--no-rosegmentを付けないとperfが正確なスタックトレースを作れない - 権限エラーが出る場合は、
perf_event_paranoidを下げる。最も緩い値は-1だが、セキュリティ要件に合うかは環境次第
生成された flamegraph.svg はClaude Codeにとって読みにくい形式です。画像として貼って「幅の広い箱を3つ挙げて」と頼むこともできますが、関数名で議論したいなら、perfが出すスタックの集計をテキストにしてから渡します。同じ考え方で、Pythonならpy-spy、Nodeならcpuprofileを前処理する手順はpy-spyの記事とcpuprofileの記事にあります。
直して、同じベンチで確かめるループ
公式のベストプラクティスは、Claude Codeに「合否が返るチェック」を渡すと、変更・実行・失敗の読み取り・再実行を自分で回せると説明しています。性能改善では、そのチェックが「baselineと比べたcriterionの出力」にあたります。
CLAUDE.md には、計測のコマンドと約束事だけを書きます。リポジトリを読めば分かる内容は、公式も「書かなくてよい」としています。
# 性能計測
- 基準の保存: cargo bench -- --save-baseline before
- 比較: cargo bench -- --baseline before --color never --noplot
- flamegraph: cargo flamegraph --bench <名前> -- --bench
- 「速くなった」は change 行が improved のときだけ書く。
"No change" や "within noise threshold" は差なしと報告する
- ベンチの入力・イテレーションの中身は、依頼されない限り変えない最後の約束は、ベンチ側を書き換えて「速くなった」と見せる抜け道をふさぐためのものです。
もう一段厳密にしたいときは、Stop フックでベンチがビルドして動くことだけ検査させます。criterionのドキュメントによると、cargo test --benches は計測と分析をせずにベンチが成功するかだけを確かめます。
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "cargo test --benches 1>&2 || exit 2" }
]
}
]
}
}フックの仕組みは公式の例(pnpm tsc --noEmit 1>&2 || exit 2)と同じです。終了コード2でClaudeの停止がブロックされ、標準エラーの内容が理由として返されます。ただし、このフックが保証するのは「ベンチがコンパイルできて動く」ことまでで、速度の改善ではありません。速度の判定は前節の --baseline 比較に残します。
つまずきやすい点
criterionの比較で読み違える3つの原因
直接実行で --bench を付け忘れる
criterionのベンチ実行ファイルは、コマンドラインに
--benchを期待します。cargoは自動で付けますが、プロファイラの下で直接実行するときは自分で付けます。比較するたびに基準が動く
--save-baselineは比較したうえで基準を上書きします。基準を固定したいときは--baselineを使います。改善率の大きさだけで誤差を判定する
区間が0をまたぐ「+0.9%」と、区間が0をまたがない「-37%」は、同じ表の中で意味が違います。依頼文の1番に判定ルールを書いておくのは、この読み違いを避けるためです。
まとめ
criterionの出力は、change: の区間とp値、判定文言、そしてoutlierとR^2の4点で読みます。この判定ルールを依頼文に書き、判定と修正を別の依頼に分けると、Claude Codeが誤差を改善と報告する事態を減らせます。
遅い関数の絞り込みには [profile.bench] debug = true と cargo flamegraph --bench <名前> -- --bench を使います。直した後は --save-baseline で保存した基準に対して再計測し、「improved」が出たときだけ成功と扱います。Rustのpanicを読む入口はbacktraceの記事にまとめてあります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。