mcpField
R
rust-performance

rust-performance

github.com/rewrite-rsその他1040 回インストール10 週間前に更新
リポジトリ

rust-performance について

Rust のコーディングエージェント向けに、プロファイリングと最適化を扱うスキルです。計測に基づく変更、ベンチマーク比較、割り当てやコレクションの選択、コンパイラー設定のトレードオフを説明します。

主な機能

  1. 01最適化前のプロファイリング
  2. 02Criterion と divan によるベンチマーク
  3. 03メモリ割り当ての再利用パターン
  4. 04アクセス方法に応じたコレクション選択
  5. 05ハッシュとコード生成のトレードオフ

活用シーン

  1. 01Rust の性能ボトルネック特定
  2. 02ベンチマーク基準との変更比較
  3. 03ホットパスのメモリ割り当て削減

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rewrite-rs/skills --skill rust-performance

インストール後、Claude Code で/rust-performanceを入力して読み込まれたか確認します。

同梱ファイルagents/openai.yamlALLOCATION.md

詳細

Rust のパフォーマンス

測定によって課題が特定されていない変更は、ここには取り込みません。プロファイルが出発点であり、コード生成フラグは最後の 5%を詰めるためのもので、最初に手をつけるものではありません。非同期処理のスループットについては /async-rust を、速度向上のための unsafe については /unsafe-rust を参照してください。

測定してから変更する

測定によって課題が特定されていない最適化は取り込みません。これを省くと、何も速くならなかった変更と、何の成果もないままコードを悪化させた変更の両方で、二重のコストがかかります。プロファイルを取れば、時間を要している関数を特定できます。それは、コードを読んでいるときに高コストに見えた関数とは、ほとんどの場合異なります。

意味のあるベンチマーク

保存済みのベースラインと統計的に比較するなら criterion、より軽量なハーネスで十分なら divan を使います。セットアップ周辺ではなく、操作そのものをベンチマークしてください。

これがないと、プロファイラーは関数名の代わりにアドレスを表示します。

[profile.bench]
debug = 1

サーマルスロットリングが発生しているノート PC でベンチマークを実行すると、数値は出ますが、比較可能な数値にはなりません。比較する実行の間は、マシンを落ち着かせておく必要があります。

たいていの答えはメモリ割り当てです

最も効果が出やすく、プロファイルで最も頻繁に原因として挙がるカテゴリです。

  • サイズがわかっている場合は Vec::with_capacity を使います。デフォルトでは容量が拡張され、そのたびにコピーが発生します。
  • 反復ごとに割り当てるのではなく、クリアして再利用します。バッファーはループの外で用意し、ループ内では clear() を呼びます。
  • 今後増えることのない所有シーケンスには、Box<[T]> と Box<str> を使います。
  • クローンせずに &mut から値を取り出すには、mem::take を使います。
  • ホットループ内で format! を使わないでください。呼び出すたびに新しい String が割り当てられます。
  • 値を借用するか所有するかを選ぶ場合は Cow を使います。値が変更された場合にのみコピーします。

合計サイズがわかっている場合の、結合処理の完全な例です。

fn joined(parts: &[String]) -> String {
    let total = parts.iter().map(|p| p.len() + 1).sum();
    let mut out = String::with_capacity(total);
    for (i, part) in parts.iter().enumerate() {
        if i > 0 {
            out.push(' ');
        }
        out.push_str(part);
    }
    out
}

再利用パターンとバッファーを渡す方法については、ALLOCATION.md を参照してください。

アクセスパターンに合ったコレクションを選ぶ

小さな n では、漸近計算量ではなくキャッシュの挙動により、Vec は HashMap より優れています。反復順序が重要な場合は BTreeMap を使います。一度構築して何度も読み取る場合は、ソート済みの Vec と binary_search を使います。切り替えどころは測定するものであり、定数として暗記するものではありません。

ハッシュ化

デフォルトのハッシャーは DoS 耐性があるぶん、低速です。これを置き換えても安全で有益なのは、キーが攻撃者によって制御されていない場合に限ります。たとえば、内部 ID、列挙型の判別値、インターン化された文字列などです。キーが攻撃者によって制御される場合、これはサービス拒否攻撃の対象となり、検索速度の向上でそのリスクを埋め合わせることはできません。置き換えを無条件の利点として示すのではなく、リスクを明確に説明してください。

イテレーターと境界チェック

イテレーターチェーンは、インデックスを使うループでは隠れてしまう長さの不変条件をオプティマイザーに伝えます。これによって境界チェックが取り除かれます。1 つのループにある 2 つの [i] アクセスを iter().zip() にすると、オプティマイザーはアクセス範囲内であることを証明できます。慣用的な形式が速い形式でもあるのはここだけです。議論すべきトレードオフはありません。

最後の 5%

LTO、codegen-units = 1、PGO、target-cpu。ADR 0007 に従い、これらはアプリケーションで決めるものであり、ライブラリのデフォルトにはしません。それぞれ、得られる利点とともに移植性へのコストを明記します。設定項目は 1 行ですが、プロファイルの取得には半日かかります。

  • コミット済みの設定に target-cpu=native を指定すると、古いマシンでは障害が発生するバイナリが生成されます。
  • PGO では代表的なワークロードが必要です。そうでないと、誤った経路に対して最適化されます。
  • LTO と codegen-units = 1 で得られるのは数パーセントの改善であり、数倍の改善ではありません。その代わり、ビルド時間が長くなります。

panic = "abort" はパフォーマンス向上のためのデフォルト設定ではありません。これはアプリケーションレベルの選択であり、ライブラリはアンワインドを前提にできず、#[should_panic] の実行時の挙動も変わります。

対象外

非同期処理のスループット、エグゼキューターの停滞、spawn_blocking については /async-rust を参照してください。これはスループットではなく、ランタイムの問題です。チェックを省くために unsafe を使う場合は /unsafe-rust を参照してください。そこでの答えは、ベンチマークではなく、そのブロックの健全性を論証することです。プロファイルで見つかったクローンがそもそも必要かどうかについては /ownership-not-clone を参照してください。

検証

cargo bench                  # against the recorded baseline where one exists
cargo clippy --all-targets   # at the level the repo configures; add -- -D warnings only if it configures none
cargo test --release

保存済みのベースラインがないベンチマークでは、変更の効果を証明できません。変更前を記録し、その後を測定して、ベンチマークが報告するノイズと比較してください。Clippy は対象リポジトリで設定されている lint レベルを維持します。-D warnings は、lint がまったく設定されていないリポジトリでのフォールバックです。

コメント