mcpField
P
performance-optimization

performance-optimization

github.com/rogervila開発054 回インストール1 日前に更新
リポジトリ

performance-optimization について

コーディングエージェント向けの再利用可能なパフォーマンス最適化スキルです。目標設定からホットパスの特定、動作確認、効果測定までを導き、正確性・安全性・保守性を保つ根拠ある変更を重視します。

主な機能

  1. 01性能目標に応じた適用基準
  2. 02ベースラインと負荷の評価
  3. 03プロファイルに基づくボトルネック特定
  4. 04動作を維持する最適化手順
  5. 05変更後の測定とトレードオフ記録

活用シーン

  1. 01レイテンシやスループット低下の調査
  2. 02頻繁に実行されるコードのメモリ削減
  3. 03データベース・I/O・並行処理の問題確認

インストール

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

$npx skills add https://github.com/rogervila/agent-skills --skill performance-optimization

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
behavior-fixtures.json
references/
php-lazy-objects.md
scripts/
validate-fixtures.js

詳細

パフォーマンスの最適化

パフォーマンスが明示的な目標である場合や、レイテンシ、スループット、メモリ、CPU、起動時間、ビルド時間、I/O、データベース負荷、並行処理、スケーラビリティに明らかなリスクがある場合は、このスキルを使用します。目的は、観測可能な動作を変えずにコードを高速化または軽量化し、測定が可能な場合は測定によって改善を検証することです。

パフォーマンスは設計上の最優先事項ですが、正確性、セキュリティ、保守性も依然として境界条件となります。同程度に明快な選択肢がある場合は、効率的なものを優先してください。大規模な書き換え、巧妙なマイクロ最適化、複雑さの追加は、測定で確認されたホットパスや高リスクのワークロードに限ってください。

適用基準

次のようなタスクでは、このスキルを使用します。

  • optimize、speed up、slow、bottleneck、latency、throughput、memory、allocation、CPU、hot path、scalability、benchmark、profile、N+1、cache、concurrency など、パフォーマンスに関する明示的な用語が含まれる場合。
  • 頻繁に実行されるコード、大量のデータを処理するコード、リクエスト/ビルド/レンダリングのループ内にあるコード、または I/O やデータベース処理を繰り返すコード。
  • アルゴリズムの計算量、過剰なメモリ割り当て、ブロッキング I/O、上限のない並行処理、リソースリークが実際のコストにつながる可能性が高い実装のレビュー。
  • ほかの点では同程度の実装から選ぶ際、可読性や正確性を実質的に損なわず、明らかに効率のよい実装を選ぶ場合。

次の場合は、このスキルを主な観点として使用しないでください。

  • ユーザーが書式、命名、ドキュメント、製品説明、スタイルの整理だけを求めている場合。
  • パフォーマンス上の懸念が示されておらず、コードが一度限りのセットアップ、移行、テスト用フィクスチャの生成、管理用スクリプトである場合。
  • 推測に基づく高速化のために、正確性、セキュリティ、アクセシビリティ、互換性、明確なリポジトリ規約を犠牲にする変更となる場合。
  • リポジトリですでに機能上のバグが発生しており、パフォーマンスが根本原因であるという根拠がない場合。

必要な入力と前提条件

最適化する前に、タスクで可能な限り次の情報を集めてください。

  • パフォーマンスの目標または症状:目標レイテンシ、スループット、メモリ上限、CPU 制限、タイムアウト、ビルド所要時間、ユーザーが感じる遅延。
  • ワークロードの特性:入力サイズ、呼び出し頻度、データ分布、並行度、環境、対象の処理が CPU バウンドか I/O バウンドか。
  • ベースライン:プロファイラーの出力、ベンチマーク結果、ログ、クエリプラン、トレース、フレームグラフ、再現手順、またはローカルで実行できる小規模なベンチマーク。
  • 継続して満たす必要がある正確性の要件と既存テスト。
  • リポジトリの制約:言語/ランタイムのバージョン、フレームワークの規約、対応プラットフォーム、依存関係、デプロイ上の制限。

ベースラインがない場合は、実用的であれば対象を絞った測定を行ってください。測定できない場合は、前提を明示し、計算量の分析や明らかに重複した処理を根拠とする、リスクの低い変更を選んでください。

最適化ワークフロー

  1. 指標を定義します。 何を改善し、何を悪化させてはならないかを明確にします。
  2. ホットパスを見つけます。 ワークロードの頻度に関連付けて、プロファイリング、トレーシング、クエリプラン、ログ、コードの調査を活用します。
  3. 動作を維持します。 リスクの高いロジックを変更する前に、対象を絞った正確性テストを実行するか追加します。
  4. 効果の最も大きい修正を選びます。 見た目だけの細かな調整よりも、アルゴリズムの改善、バッチ処理、アロケーションの削減、ストリーミング、インデックス、キャッシュ、または制限付き同時実行を優先します。
  5. 変更範囲を狭く保ちます。 大規模な書き換えや無関係なリファクタリングを避け、ボトルネックを最適化します。
  6. 再度測定します。 同じワークロードで変更前後の数値を比較するか、静的検証しかできなかった理由を説明します。
  7. トレードオフを記録します。 最適化によって生じた複雑さ、キャッシュの無効化、同時実行の制限、環境に関する前提条件を記載します。

コードを書く前に、立ち止まって次の点を確認してください。

  1. このワークロードで最も大きなコストは何ですか?
  2. アルゴリズムの計算量、アロケーション、I/O の往復、ブロッキング、または競合を減らせますか?
  3. 動作が変わっていないことと、対象の指標が実際に改善したことをどう検証しますか?

1. ループと反復処理

ループはすべてコストを増幅させます。ループ内の処理を最小限に抑えましょう。

  • 不変の値をループ内で再計算しないでください。 定数式、長さ、検索結果はループの外に出します。
  • ハッシュマップやインデックスを使った一度の走査で済む場合は、ネストしたループを避けてください。 O(n) や O(n log n) が可能なら、O(n²) はほとんどの場合許容できません。
  • 実行環境による最適化が期待できる場合は、手動のループよりも言語組み込みの一括操作(map、filter、reduce、リスト内包表記、ベクトル化演算)を優先します。
  • 早めに終了します。 答えが見つかったら、反復を止めます。break、早期リターン、短絡評価を使います。
  • ループを融合します。 同じコレクションを反復する連続した2つのループは、ロジック上可能であれば1回の走査にまとめます。
  • ループ内でのアロケーションを避けてください。 一度だけ作成して再利用するか、事前に確保できるオブジェクト、文字列、配列をループ本体内で作成しないでください。
// BAD — recomputes len, creates string each iteration
for (int i = 0; i < list.size(); i++) {
    String key = prefix + ":" + list.get(i).getName();
    map.put(key, list.get(i));
}

// GOOD — hoisted size, reused builder, single access
int n = list.size();
StringBuilder sb = new StringBuilder(prefix).append(':');
int prefixLen = sb.length();
for (int i = 0; i < n; i++) {
    Item item = list.get(i);
    sb.setLength(prefixLen);
    sb.append(item.getName());
    map.put(sb.toString(), item);
}

2. 変数と一時値

変数を使うたびに、レジスターまたはスタック領域が必要になります。必要なものだけを使いましょう。

  • 不要な一時値をなくします。 値を一度しか使わず、インラインで記述しても読みやすい場合は、インライン化します。
  • 読み取らない変数に値を代入しないでください。 不要な書き込みはサイクルを浪費し、コードを分かりにくくします。
  • 式が明確な場合は、一時変数に代入してから返すのではなく、直接リターンすることを優先します。
  • すぐに破棄する中間コレクションを作るのではなく、アキュムレーターを再利用します。
// BAD — pointless temporary
result = compute_value(x)
return result

// GOOD — direct return
return compute_value(x)
// BAD — builds full list just to check existence
temp = [item for item in collection if item.matches(criteria)]
found = len(temp) > 0

// GOOD — short-circuit, no allocation
found = any(item.matches(criteria) for item in collection)

3. メモリとアロケーション

アロケーションにはコストがかかります。最も速いアロケーションは、そもそも発生しないものです。

  • サイズが分かっているか推定できる場合は、コレクションを事前に確保します。配列、リスト、マップのサイズ変更にはコストがかかります。
  • 反復処理(文字列の構築、バイトバッファー、一時配列)には、バッファーを再利用します。
  • ライフタイムが現在のスコープ内に限られる場合は、ヒープよりもスタックアロケーション(値型、構造体、スタック配列)を優先します。
  • ボックス化型と非ボックス化型を区別する言語(Java、C#、Kotlin)では、プリミティブ型のボックス化を避けます。
  • パフォーマンスが重要なパスで頻繁に発生する短命のアロケーションには、オブジェクトプールやアリーナを使用します。
  • 部分配列や部分文字列をコピーするより、ビューやスライスを優先します。変更が不要な場合は、元データを参照します。
  • 参照を早めに解放します。 大きなオブジェクトが不要になったら、特に時間のかかる処理の前に、参照を null にするか別の値に再代入します。
  • 適切なデータ構造のサイズを選びます。 10,000件を格納すると分かっているのに、デフォルト容量が16の HashMap<K,V> を使わないでください。
// BAD — grows the list dynamically, multiple reallocations
items = []
for row in rows:
    items.append(transform(row))

// GOOD — pre-allocated
items = [None] * len(rows)
for i, row in enumerate(rows):
    items[i] = transform(row)

// BEST — generator if downstream consumes lazily
items = (transform(row) for row in rows)

4. アルゴリズムとデータ構造

パフォーマンスを大きく向上させるには、誤ったアルゴリズムを細かく調整するのではなく、適切なアルゴリズムを選ぶことが重要です。

  • 計算量を把握しましょう。 解決策を書く前に、時間計算量と空間計算量を Big-O で示します。必要以上に大きい場合は、より適切な方法を探しましょう。
  • ハッシュベースの検索(セット、マップ、辞書)をメンバーシップテストやキー検索に使いましょう。線形スキャンの O(n) に対し、O(1) で検索できます。
  • 必要なときだけソートし、適切なソート方法を選びましょう。 最小値や最大値を見つけるためにソートする必要はありません。一度走査するだけで十分です。
  • 適切なデータ構造を使いましょう。 先頭・末尾の操作には両端キュー、上位 k 件の取得にはヒープ、前方一致検索にはトライ、フラグにはビットセットを使います。
  • コストの高い計算をキャッシュしましょう。 入力が繰り返される純粋関数にはメモ化を適用します。定数の対応付けにはルックアップテーブルを使います。
  • 冗長な計算を避けましょう。 同じスコープ内で、導出値を同じ方法で二度計算してはいけません。一度計算して保存し、再利用します。

5. 文字列操作

文字列のコストは見えにくいものです。連結、フォーマット、エンコードは、隠れたコスト要因になります。

  • 複数段階の連結には文字列ビルダーやバッファーを使いましょう。 特にループ内では重要です。ループ内で + を使って連結してはいけません。
  • 言語が最適化する場合は、繰り返し連結するよりも補間やフォーマットを使いましょう。
  • 解析の繰り返しを避けましょう。 文字列は一度だけ解析し(日時、数値、列挙型などに変換し)、解析済みの値を後続の処理に渡します。
  • インターンされた文字列やシンボルが利用できる場合は、等価性を比較する前に同一性を比較しましょう。
  • Unicode の意味論が不要な場合は、バイトレベルの操作を使いましょう(例:ASCII プロトコルの解析)。

6. I/O とネットワーク

I/O は計算より桁違いに遅くなります。バッチ処理やバッファリングを活用し、往復回数を最小限に抑えましょう。

  • データベースクエリをバッチ処理しましょう。 ループ内でクエリを実行してはいけません(N+1 問題)。一括取得、JOIN、IN 句を使います。
  • I/O をバッファリングしましょう。 バッファーなしのストリームは、バッファー付きのラッパーで包みます。書き込みごとにフラッシュせず、適切なタイミングでフラッシュします。
  • ネットワークの往復回数を最小限に抑えましょう。 API 呼び出しをまとめ、バッチエンドポイントを使い、必要なデータを事前に取得します。
  • 大きなペイロードには、すべてをメモリに読み込むのではなく、ストリーミングを使いましょう。
  • データベース、HTTP クライアント、再利用可能なあらゆる接続には、コネクションプーリングを使いましょう。
  • 適切なタイムアウトを設定しましょう。 上限のない待機は、スレッドやリソースを無駄にします。
  • 数百万行をメモリに読み込むのではなく、大きな結果セットをページネーションしましょう。
// BAD — N+1 queries
for user in users:
    orders = db.query("SELECT * FROM orders WHERE user_id = ?", user.id)

// GOOD — single batch query
user_ids = [u.id for u in users]
all_orders = db.query("SELECT * FROM orders WHERE user_id IN (?)", user_ids)
orders_by_user = group_by(all_orders, key=lambda o: o.user_id)

7. 並行処理と並列化

I/O バウンドの処理には並行処理を使いましょう。CPU バウンドの処理には並列処理を使いましょう。違いを理解することが重要です。

  • 独立した処理を特定し、並行して実行しましょう(async/await、futures、goroutines、スレッド)。
  • ネットワーク呼び出し、ファイルの読み取り、データベースクエリなど、同時に進められる処理には非同期 I/O を使いましょう。
  • CPU バウンドの処理は、スレッドプール、ワーカープール、並列ストリームを使ってコア間で並列化しましょう。 ただし、処理負荷がオーバーヘッドに見合う場合に限ります。
  • 共有された可変状態を避けましょう。 イミュータブルなデータ、メッセージパッシング、ロックフリー構造を優先します。ロックが必要な場合は、クリティカルセクションを最小限に抑えます。
  • リソースの過負荷を防ぐため、並行処理の上限を設けて作業をバッチ処理しましょう(セマフォ、タスクキュー)。
  • 並列配列や隣接するアトミックカウンターを使う場合は、キャッシュラインにおけるフォルスシェアリングに注意しましょう。
// BAD — sequential I/O calls
result_a = await fetch(url_a)
result_b = await fetch(url_b)
result_c = await fetch(url_c)

// GOOD — concurrent I/O
[result_a, result_b, result_c] = await Promise.all([
    fetch(url_a),
    fetch(url_b),
    fetch(url_c),
])

8. 遅延評価と短絡評価

使わないものを計算してはいけません。

  • 呼び出し側がすべての結果を必要としない可能性がある場合は、遅延評価(ジェネレーター、イテレーター、ストリーム)を使いましょう。
  • 条件式はコストと成立しやすさを考慮して並べましょう。 最もコストが低く、短絡しやすい条件を先に置きます。
  • 値に実際にアクセスされるまで、コストの高い初期化を遅らせましょう(遅延プロパティ、ダブルチェックロック、once パターン)。
  • クエリではページネーションと上限を使いましょう。 10 件を表示するために、すべての行を取得してはいけません。
// BAD — evaluates expensive condition even when cheap one fails
if (expensiveCheck(x) && cheapCheck(x)) { ... }

// GOOD — cheap check first, expensive only if needed
if (cheapCheck(x) && expensiveCheck(x)) { ... }

9. キャッシュ

最も速い計算は、すでに実行済みの計算です。

  • 入力が繰り返される場合は、純粋関数の結果をキャッシュします(メモ化)。
  • 適切なキャッシュの削除方式(LRU、TTL、サイズ制限付き)を使用し、メモリが際限なく増加するのを防ぎます。
  • 適切な層でキャッシュします。頻繁に使われるデータにはメモリ内キャッシュ、共有状態には Redis/Memcached、API レスポンスには HTTP キャッシュ、静的アセットには CDN を使用します。
  • 正しく無効化します。古くなったキャッシュは、キャッシュがない場合よりも悪い結果を招きます。古くなっても許容できるデータには、時間ベースの有効期限を設定します。
  • キャッシュの前にプロファイリングします。実際にコストの高い処理だけをキャッシュします。安価な計算をキャッシュしても、メリットがないまま複雑さが増すだけです。

10. データのシリアライズとデシリアライズ

データの解析とフォーマットは、あらゆる境界で発生する隠れたコストです。

  • 適切な形式を選びます。 サービス間の内部通信では、バイナリ形式(Protocol Buffers、MessagePack、FlatBuffers)はテキスト形式(JSON、XML)よりも高速です。人間が読みやすいことが重要なシステム境界でのみ、テキスト形式を使用します。
  • シリアライズは一度だけ行います。 オブジェクトを JSON に変換し、その文字列を渡してから、同じプロセスの反対側で再度解析するのは避けます。オブジェクトをそのまま渡します。
  • ドキュメント全体を DOM ツリーに読み込むのではなく、大きなペイロードにはストリーミングパーサー(SAX、JsonReader、ijson)を使用します。
  • シリアライズの繰り返しを避けます。 同じオブジェクトを複数回シリアライズする場合(ログ記録とレスポンスへの使用など)は、一度だけシリアライズして結果を再利用します。
  • パフォーマンスが重要な処理では、リフレクションベースのシリアライズ(Java のデフォルトの Serializable、Python の pickle)よりも、スキーマ駆動型のシリアライズ(protobuf、Avro)を優先します。
  • エンコードのコストに注意します。 Base64 エンコードではデータ量が約 33%増加します。ペイロードサイズが重要な場合は、エンコードの前に圧縮します。
  • 必要なものだけをデシリアライズします。50 個のフィールドを持つ JSON オブジェクトから 3 つのフィールドだけが必要なら、部分パーサーまたはプロジェクションを使用し、オブジェクト全体をデシリアライズしないでください。
// BAD — full deserialization when only one field is needed
data = json.parse(payload)
user_id = data["user"]["id"]

// GOOD — targeted extraction (language-dependent)
user_id = json_extract(payload, "$.user.id")

11. エラー処理

例外はコストがかかります。制御フローには決して使用しないでください。

  • 想定される状況に対して例外をスローしないでください。 ユーザーが無効な入力を送信することは例外的な状況ではなく、通常のことです。結果型、エラーコード、または検証結果の返却を使用します。
  • ホットループ内で try/catch を避けます。 多くのランタイムでは、try ブロックに入るだけでもオーバーヘッドがあり、例外のスローには非常に大きなコスト(スタックの巻き戻し、トレースの取得)がかかります。
  • メソッド全体を try/catch で囲むよりも、ガード節(無効な入力に対する早期リターン)を優先します。
  • トレースが読み取られることのない場合は、可能な言語では既知のエラー型のスタックトレースを無効にします(例:Java で fillInStackTrace() をオーバーライドする)。
  • パフォーマンスが重要なコードでは、例外ベースのフローではなく、エラー値パターン(Result<T, E>、Option<T>、(value, err) タプル)を使用します。
// BAD — exception as control flow in a tight loop
for item in items:
    try:
        value = int(item)
        process(value)
    except ValueError:
        continue

// GOOD — check before converting
for item in items:
    if item.isdigit():
        process(int(item))

12. ロギングと可観測性

ロギングは、プロファイルを圧迫するまでは見えにくいオーバーヘッドです。

  • コストの高いログメッセージの生成をガードします。ログレベルが無効なときは、ログメッセージの文字列を構築しないでください。遅延評価、ラムダ、またはレベルチェックによるガードを使用します。
  • レベルチェックでガードされており、TRACE/DEBUG レベルの場合を除き、タイトなループ内では決してログを記録しないでください。
  • 文字列フォーマットよりも、構造化ロギング(キーと値のペア)を使用します。構造化ロガーは、実際にログを書き込むまでシリアライズを遅延します。
  • スループットの高い処理では、非同期またはバッファリングされたロギングを使用します。ディスクやネットワークへの同期ログ書き込みは、呼び出し元のスレッドをブロックします。
  • 大きなオブジェクトのログ記録を避けます。 INFO レベルでコレクション全体やリクエスト本文を toString() しないでください。代わりに識別子やサイズを記録します。
  • 高頻度イベントをサンプリングします。イベントが 1 秒間に何千回も発生する場合は、すべての発生を記録するのではなく、N 回に 1 回を記録します。
// BAD — string is always constructed, even if DEBUG is off
logger.debug("Processing user " + user.toString() + " with orders " + orders.toString())

// GOOD — deferred evaluation, no construction if level is off
logger.debug("Processing user {} with {} orders", user.id, orders.size())

13. 正規表現

正規表現は強力ですが、見かけ以上にコストがかかります。動作する最もシンプルな方法を使いましょう。

  • 一度コンパイルして再利用します。 ループ内や関数呼び出しのたびに正規表現パターンをコンパイルしないでください。定数、static final、またはモジュールレベルの変数としてコンパイルします。
  • 単純な文字列メソッド(startsWith、endsWith、contains、indexOf、split)を優先します。一致条件がリテラル、またはごく単純な場合に適しています。
  • 破滅的なバックトラッキングを避けます。 (a+)+ や (a|a)+ のような入れ子の量指定子は、実行時間を指数関数的に増加させます。利用できる場合は、アトミックグループや占有量指定子を使います。
  • 一致値が不要な場合は、キャプチャグループ (...) の代わりに非キャプチャグループ (?:...) を使います。キャプチャにはメモリが割り当てられます。
  • パターンにアンカーを付けます。 ^ と $ のアンカーを使い、エンジンが文字列全体を不必要に走査しないようにします。
  • 複雑なパターンは、手書きのパーサーと比較してベンチマークします。 ログやプロトコルなど、高頻度の解析では、ステートマシンのほうが正規表現より高性能な場合がよくあります。
// BAD — recompiles regex on each call
def extract_email(text):
    match = re.search(r'[\w.+-]+@[\w-]+\.[\w.-]+', text)
    return match.group(0) if match else None

// GOOD — compiled once at module level
EMAIL_PATTERN = re.compile(r'[\w.+-]+@[\w-]+\.[\w.-]+')

def extract_email(text):
    match = EMAIL_PATTERN.search(text)
    return match.group(0) if match else None

14. リソースのライフサイクル

リソースのリークは、徐々にパフォーマンスを低下させます。

  • 開いたものは必ず閉じます。 言語が提供するパターンを使います。try-with-resources(Java)、using(C#)、with(Python)、defer(Go)、RAII(C++/Rust)などです。
  • リソースのスコープを厳密にします。 ファイル、データベース接続、ロックを開くタイミングはできるだけ遅くし、解放するタイミングはできるだけ早くします。長時間実行される処理の間、リソースを保持しないでください。
  • クリーンアップにファイナライザーやデストラクターを使わないでください。 これらは予測できないタイミングで実行される(または実行されない)うえ、GC のオーバーヘッドも増加させます。確実に実行されるクリーンアップを使います。
  • 接続のリークに注意します。 プールに返却されないデータベース接続、HTTP クライアント、ソケットは、プールを枯渇させ、最終的にはすべての処理を停滞させます。
  • リソースの消費を制限します。 制御不能な増加を防ぐため、容量制限付きキュー、レート制限、最大接続数、メモリ上限を設定します。
// BAD — connection leak if process() throws
conn = pool.get_connection()
result = process(conn)
conn.close()

// GOOD — guaranteed cleanup
with pool.get_connection() as conn:
    result = process(conn)

15. データの局所性とキャッシュ効率

最も高速なデータは、すでに CPU キャッシュにあるデータです。

  • メモリに順次アクセスします。 キャッシュのプリフェッチにより、配列を線形に走査するほうが、リンクリストやツリー内のポインターをたどるよりも大幅に高速です。
  • 頻繁に使うデータをまとめます。 一緒にアクセスされるフィールドは、同じ構造体/オブジェクトにまとめます。アクセス頻度の低いフィールドは、補助的な構造に分けます。
  • 走査順が予測できる場合は、リンク構造(リンクリスト、ノードごとにポインターを持つツリー)よりも配列/ベクターを優先します。
  • 多数のエンティティにわたって単一のフィールドを処理する場合(データ量の多いコードやゲーム/シミュレーションコードでよくあります)は、Array-of-Structs(AoS)よりStruct-of-Arrays(SoA)を検討します。
  • ポインターの間接参照を最小限にします。 ポインターをたどるたびに、キャッシュミスが発生する可能性があります。可能な場合は、入れ子になったオブジェクトを平坦化します。
  • メモリのアラインメントとパディングに注意します。 レイアウトを制御できる言語では、パディングによる無駄を最小限に抑えるため、構造体のフィールドをサイズの大きい順に並べます。
  • 連続した領域に割り当てます。 一緒に作成・処理されるオブジェクトには、フラットバッファーやアリーナを事前に割り当てます。
// SLOW — random access pattern, poor locality
for node in linked_list:
    total += node.value     // each node is a pointer chase

// FAST — sequential access, cache-friendly
for i in range(len(values)):
    total += values[i]      // contiguous memory, hardware prefetch

16. 言語固有の特性を考慮する

使用する言語のパフォーマンス特性を把握してください。

このスキルは特定の言語に依存しませんが、エージェントは必要に応じて言語固有の知識を適用する必要があります。

  • JVM(Java、Kotlin、Scala): オートボクシングを避け、プリミティブストリームを優先し、GC の負荷に注意し、StringBuilder を使用し、適切なコレクション実装を選択してください。
  • Python: リストよりジェネレーターを使用し、collections モジュールの型を優先し、ベクトル化計算には NumPy/pandas を使用し、CPU の並列処理ではグローバルインタープリターロックを避けて(multiprocessing を使用して)ください。
  • JavaScript/TypeScript: メガモーフィックな呼び出しサイトを避け、数値処理には型付き配列を優先し、CPU 負荷の高いタスクには Web Workers を使用し、イベントループのブロックに注意してください。
  • Go: ヒープにエスケープするアロケーションを最小限に抑え、再利用可能なオブジェクトには sync.Pool を使用し、小さな構造体には値レシーバーを優先し、並行実行数に上限を設けてゴルーチンを使用してください。
  • Rust: スタックアロケーションを優先し、インデックス付きループよりイテレーターを使用し、不要なクローンを避け、ゼロコスト抽象化を活用してください。
  • C/C++: キャッシュ局所性(構造体の配列と配列の構造体)を考慮し、適用可能な場合は SIMD を使用し、コピーよりムーブセマンティクスを優先し、ホットパスでの仮想ディスパッチを最小限に抑えてください。
  • SQL: インデックスを使用し、SELECT * を避け、EXPLAIN でクエリプランを把握し、カーソルより集合ベースの操作を優先してください。
  • PHP: 手動ループよりネイティブの配列関数を優先し、大規模なデータセットにはジェネレーターを使用し、不要なオブジェクトのインスタンス化を避け、opcache を効果的に活用してください。
  • C#/.NET: アロケーションを避けるために Span<T> と Memory<T> を使用し、ホットな非同期パスでは ValueTask を優先し、一時バッファーには ArrayPool を使用し、デリゲートのオーバーヘッドが生じるためホットパスでは LINQ を避けてください。

17. 最適化すべきでないもの

早すぎる最適化は諸悪の根源ですが、意図的な怠慢も同様です。

次の場合は、可読性を犠牲にしてマイクロ最適化しないでください。

  • コードを一度しか実行しない場合(スクリプト、マイグレーション、セットアップ)。
  • 処理がすでに 1 ミリ秒未満で、ホットパスにもない場合。
  • プロファイルでボトルネックが別の場所にあると分かっている場合。

ただし、次の場合は必ず最適化してください。

  • コードがループ内、リクエストハンドラー内、またはホットパスで実行される場合。
  • 処理に I/O、ネットワーク、または大規模なデータが関わる場合。
  • 効率的な方法と非効率な方法の読みやすさが同程度の場合(デフォルトでは効率的な方法を優先してください)。
  • 複雑さを増やさずに、より優れたアルゴリズムやデータ構造を使用できる場合。

このスキルの適用

コードを記述またはレビューする際は、次のチェックリストに従ってください。

  1. ホットパスを特定する。 このコードは最も頻繁にどこで実行されるでしょうか?
  2. 適切なアルゴリズムを選ぶ。 この問題に対して Big-O は最適でしょうか?
  3. アロケーションを最小限に抑える。 事前に確保、再利用、またはオブジェクトの生成を回避できますか?
  4. 重複する処理をなくす。 同じ計算を二度行っていませんか?キャッシュできますか?
  5. I/O をまとめる。 ディスク、ネットワーク、またはデータベースへの不要な往復が発生していませんか?
  6. 並行処理を活用する。 並列実行できる独立した処理はありますか?
  7. 早期に短絡する。 ループ、条件分岐、または関数から早期に抜け出せますか?
  8. ログに注意する。 ログメッセージの構築は、レベルチェック後に行われるようになっていますか?
  9. シリアライズは一度だけ行う。 境界をまたいでデータを不必要に変換し直していませんか?
  10. エラー処理のコストを抑える。 ホットパスで例外を制御フローに使用していませんか?
  11. リソースを解放する。 接続、ハンドル、ロックを確実に解放していますか?
  12. 局所性を考慮する。 データレイアウトはアクセスパターンに対してキャッシュフレンドリーですか?
  13. データで検証する。 迷ったらプロファイリングしてください。ボトルネックの場所を推測しないでください。

コードのすべての行にはコストがかかります。ワークロードに見合う場合にのみ、複雑さを加えてください。

クイックスタートのワークフロー

  1. 分析:プロファイリングツール、または根拠のあるコード調査を使って、ホットパス、ボトルネック、目標指標を特定します。
  2. リファクタリング:複雑度の改善、アロケーションの削減、バッチ処理、ストリーミング、インデックス作成、キャッシュ、同時実行数の制限、同期処理の削減など、効果が見込める最小限の変更を適用します。
  3. 検証:正しさを確認し、可能であれば同じワークロードで変更前後のパフォーマンスを比較します。

検証と完了

以下を満たした場合、このスキルは正常に適用されています。

  • 目標指標とワークロードが明示されているか、エージェントが使用した前提を明確に説明しています。
  • プロファイラーデータ、ベンチマークデータ、クエリプラン、ログ、トレース、または明確な計算量の分析に基づいて、考えられるボトルネックが特定されています。
  • 実装が観測可能な動作を維持し、既存のテストに合格しています。
  • 最適化がボトルネックに限定され、無関係な書き換えを避けています。
  • 測定が可能な場合は、コマンド、ワークロード、サンプル数、結果を含む変更前後の根拠が報告されています。
  • ベンチマークのノイズや環境の制約によって結果が不確かな場合、最終回答でその旨を伝え、効果を誇張しません。

利用できる場合は、リポジトリに既存のパフォーマンスツールを使用してください。例:ベンチマークスイート、負荷テスト、プロファイラー、データベースの EXPLAIN、フレームグラフ、ブラウザーのパフォーマンスツール、メモリプロファイラー、ランタイムトレーシング。プロジェクトに適しており、ユーザーが継続的に実行するパフォーマンステストを求めている場合を除き、新しいベンチマークフレームワークは追加しないでください。

失敗時の対応とエスカレーション

以下の場合は推測せず、ユーザーに確認するか、作業を中止してください。

  • 目標のワークロード、想定規模、またはパフォーマンス指標が重要であるにもかかわらず、不明な場合。
  • 提案する最適化によって、ユーザーから見える動作、データの一貫性、順序、セキュリティ、アクセシビリティ、互換性が変わる場合。
  • 実行可能な改善策が、アーキテクチャの変更、新たなインフラ、有料サービス、スキーマの移行、または運用上のリスクを伴う場合。
  • 既存のテストがなく、正しさを保証するテストを先に追加するか依頼しないと、コードパスの最適化が危険すぎる場合。

パフォーマンスがユーザーの依頼に関係しない場合、このスキルを無理に適用しないでください。同等にシンプルな範囲で効率的なコードを選びつつ、通常のリポジトリの慣習を優先してください。

出力要件

パフォーマンスに関する作業を完了する際は、以下を含めてください。

  • 対処したボトルネックまたはリスク。
  • 実施した最適化と、それによって目標指標が改善されると考える理由。
  • 実施した正しさの確認。
  • 実施したパフォーマンスの確認。可能な場合は変更前後の結果も含めてください。
  • 残るリスク、測定上の制約、または今後プロファイリングすべき箇所。

参考ドキュメント

このスキルは一般的なパフォーマンス原則に従っています。現時点で外部の参考資料は必要ありません。

スクリプトとアセット

  • assets/behavior-fixtures.json - パフォーマンス最適化の適用、非適用、測定、検証に関するガイダンス用の動作フィクスチャです。
  • scripts/validate-fixtures.js - このスキルとその動作フィクスチャが、パフォーマンス最適化に必要な境界条件を網羅していることを検証します。

ライセンス情報

このスキルは MIT License の下でライセンスされています。

コメント