mcpField
D
dotnet-inspect-performance

dotnet-inspect-performance

github.com/richlander開発159411 回インストール今日更新
リポジトリ

dotnet-inspect-performance について

dotnet-inspectで.NETアセンブリの最適化候補を順位付けし、対処可能なコードパターンを調べるスキルです。コールグラフ上の影響度とパフォーマンス・リソース分析を組み合わせ、静的検出は実行時検証が必要な候補として扱います。

主な機能

  1. 01コールグラフの影響度でメンバーを順位付け
  2. 02種類別のパフォーマンスパターン分析
  3. 03優先度と確信度による候補の絞り込み
  4. 04割り当てと呼び出し箇所の証拠追跡
  5. 05例外経路のArrayPool churnの特定

活用シーン

  1. 01最適化レビュー対象メソッドの選定
  2. 02ループ内で繰り返す割り当ての発見
  3. 03文字列生成候補の調査
  4. 04プロファイリング対象のプール churn の選定

インストール

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

$npx skills add https://github.com/richlander/dotnet-inspect --skill dotnet-inspect-performance

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

詳細

dotnet-inspect:パフォーマンス分析とトリアージ

このスキルを使うと、.NET アセンブリ内で最初に最適化または堅牢化する価値のあるメンバーを見つけ、実行可能な書き換えパターンに照らしてトリアージできます。この分析は実験的なものであり、セクション名やシグナルのセットはリリースごとに変更される場合があります。

dnx dotnet-inspect -y -- <command>

まずレバレッジで順位付けする

Top Leverage は、呼び出しグラフ上のレバレッジに基づいてメンバーを順位付けします。直接の呼び出し元、Root Reach(メンバーに推移的に到達する個別のエントリポイント)、ファンアウト、深さ、ループ内の呼び出しを評価します。ライブラリ全体を対象にする場合はここから始め、その後、型に絞り込みます。

dnx dotnet-inspect -y -- library MyLib.dll -S "Top Leverage"
dnx dotnet-inspect -y -- type MyType --library MyLib.dll --all -S "Top Leverage"

順位付けされた行には、コピー可能な Stable セレクター、Visibility、Selector が含まれます。非公開メンバーも含めるには、--all を追加します。

順位付けされた候補ではなく、ライブラリ全体の構造レポートを表示するには、明示的な Library Metrics セクションを選択します。

dnx dotnet-inspect -y -- library MyLib.dll -S "Library Metrics"

このセクションでは、Research が所有するコンパイル済み IL の母集団メトリクスを報告します。総合スコアやソースレベルの意味付けは行いません。

書き換えパターンに照らしてトリアージする

ライブラリのトリアージは、@Performance 配下の種類別セクション(Performance: Boxing、Performance: Arrays、Performance: Strings、Performance: Closures and Delegates など)に分かれています。構造上の検出では、分析を実行せずに作成済みの種類を一覧表示します。このライブラリで検出結果のある種類だけを残すには、--effective を追加します。カウントでは選択したグループが実行され、行数がゼロの種類も含まれます。型またはメンバーのスコープでは、対象を絞った Performance Triage のレンズが維持されます。

dnx dotnet-inspect -y -- library MyLib.dll -D @Performance
dnx dotnet-inspect -y -- library MyLib.dll -D @Performance --effective
dnx dotnet-inspect -y -- library MyLib.dll -S @Performance --count
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance: Boxing" --jsonl
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance: Strings" --jsonl
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance:*" \
  --where "Priority>=high" --top 20 --tsv
dnx dotnet-inspect -y -- library MyLib.dll \
  --triage-shape scan-method-in-loop-call,scan-method-in-recursive-traversal,linq-scan-in-loop,string-build-in-loop \
  --top 20 --tsv
dnx dotnet-inspect -y -- library MyLib.dll --triage-shape capturing-delegate --top 10 --jsonl

対象とするのは IL から確認できるコスト(割り当て:box、newarr、delegate の newobj、ToArray/ToList/Concat)であり、JIT が処理する懸念事項(isinst/castclass の折りたたみ、非仮想化、境界チェックの除去、null チェックの折りたたみ)ではありません。

シグナル密度の高い最初のパスには --where "Priority>=high"、繰り返し発生するコストには --loop、エビデンス/書き換えの確信度の下限には --min-confidence high|medium|low、1 つ以上のパターンには --triage-shape、キュレーション済みの順位付き先頭部分には --top N を使います。これらのフラグを指定すると、該当するパフォーマンスのレンズが自動的に選択されます。ライブラリの行形式では、複数の種類のセクションに行が含まれる場合、Performance:* は先頭に Kind 列を付けて 2 つ以上の種類のセクションを 1 つのテーブルにまとめます。フィルタリング後に行が含まれる種類が 1 つだけの場合、行形式では Kind 列のない、その種類固有のスキーマが使用されます。種類の識別子を明示的に維持するには、構造化された --json を使ってください。@Performance には異種のセクションも含まれるため、検出、カウント、Markdown、または JSON ドキュメントにはこちらを使ってください。--top は描画前に順位付きデータを絞り込み、--rows N はその後に描画する行数を制限します。よく使われるパターンには、capturing-delegate、box-value-type、generic-parameter-object-box(制約のないジェネリック値が object.Equals のためにボックス化されるパターンです。値型のインスタンス化でのみ割り当てが発生し、ループのエビデンスによって繰り返しが証明されない限り、中優先度で始まります)、small-array、cache-lookup-factory-delegate(ConcurrentDictionary.GetOrAdd に渡される、呼び出しごとのインスタンスファクトリー)、linq-scan-in-loop、scan-method-in-loop-call(呼び出し元のループ内から呼び出される線形スキャンのヘルパー)、scan-method-in-recursive-traversal(再帰トラバーサルの各ノードで繰り返されるスキャン)、materialize-in-loop(ループ不変の ToArray/ToList をループの外に移動できるパターン)、string-build-in-loop、string-materialization、enumerator-allocation、async-state-machine、sync-call-in-async(シグネチャに互換性のある Async の兄弟 API がある同期 API を、非同期メソッドが呼び出すパターン)、allocation-hotspot があります。アルゴリズムに関するパターンは、明示的に問い合わせてください。静的分析では、スキャン対象のシーケンスがループやトラバーサルに伴って増加することを証明できないため、スキャンヘルパーの確信度は低いままです。そのため、--min-confidence high を指定したパスでは意図的に除外されます。 string-materialization 行は、文字列を生成する可能性がある正確なフレームワーク操作を示します。対象は、連結、結合、書式指定、String.Create、文字列コンストラクター、補間文字列ハンドラーの最終化、StringBuilder.ToString、および Encoding.GetString です。Operation は、たとえば string.concat や string.builder-finalization のように、方式を示します。これらはマテリアライズの可能性を示すものであり、割り当てを保証するものではありません。新しいオブジェクトの割り当て、バイト数、頻度、書き換えの安全性は確定できません。候補を変更する前に、代表的な割り当てトレースまたはベンチマークで確認してください。最初のバージョンはプロシージャ内のみを対象とします。文字列の生成をヘルパーに委譲する呼び出し元には、そのヘルパーの正確な Finding は引き継がれません。

sync-call-in-async 候補を選択した後、メンバーの Call Graph で --fields AsyncAlternatives を指定して、機会の件数をレバレッジビューに反映します:

dnx dotnet-inspect -y -- member MyType ReadAsync:1 --library MyLib.dll \
  -S "Call Graph" --fields "Fanin,Depth,Loop,AsyncAlternatives"

グラフの手掛かりは、ソースメンバー単位のコンテキストであり、トリアージの代わりではありません。正確な Finding、物理的な EvidenceMethod/IL の根拠、および提案される非同期の対応メソッドについては、Performance Triage を使用してください。従来型の非同期メソッドでは、物理的な呼び出しは生成された MoveNext 内にあるため、グラフはその同期 API へのソースメソッドからの直接エッジを意図的に捏造しません。

既定の Triage の順序では、Priority と Confidence を分けています。 Priority は静的な実行可能性の判断です。直接証拠のあるアルゴリズム上の増幅、回避可能なキャッシュ検索時のファクトリー割り当て、および対処可能な高い割り当て比重は高く評価されます。共有元の同一性を確認できない再帰的なスキャンヘルパーや、その他の一般的な繰り返しコストは中程度に評価されます。通常の一度限りの候補は低く評価されます。エスケープが不明な small-array 行は、スタック上への安全な書き換えが証明されていないため、比重が高くても中程度にとどまります。Confidence は証拠と提案される書き換えの確実性を表すため、優先度が高く確信度が低い行は、実行時の効果を主張するものではなく、早期に調査すべき対象として意図されています。フラット化された Performance:* 行の出力では、種別をまたいでこの全体順序が維持されます。

ローカルな書き換えパターンに当てはまらないレジストリ、パイプライン、またはオブジェクトグラフの構築については、集約された割り当てファンアウトを有効にします:

dnx dotnet-inspect -y -- library MyLib.dll \
  --triage-shape allocation-fanout \
  --order-by "OncePaths desc" --top 20 --tsv

Direct Sites はメソッド内に限られます。Once Paths は、アセンブリ内の正確な呼び出し箇所を合成し、繰り返される呼び出し箇所を個別に数えます。条件付き、反復、不明、キャッシュ済み、不透明なパスは、それぞれ別の列に保持されます。これは IL から確認できる正常リターンパス上の量として扱い、実行時のバイト数や観測された頻度とはみなさないでください。Opaque Paths の件数が多い場合は、仮想呼び出し、外部処理、デリゲート、再帰、またはランタイムライブラリの処理について、さらに掘り下げるかプロファイラーで調査する必要があります。

正確な行は、構造化 JSON 内でネイティブの Analysis 生成元に由来する機械可読な来歴情報を保持します: Candidate、Finding(analysis.allocation、analysis.call-site、または analysis.string-materialization)、 Provenance=exact、 Assembly、ModuleVersionId、MethodToken、Operation、Token、 EvidenceMethod、および IL。 MethodToken はソースに対応するメンバーを識別します。一方、命令が別個の MethodDef に対応付けられている場合は、EvidenceMethod が含まれます。非同期のソースメンバーでは、IL に表示されるオフセットを持つ生成済み MoveNext 本体を示すことがあります。正確な本体位置は Assembly +(存在する場合は EvidenceMethod、それ以外は MethodToken)+ IL です。静的入力に ModuleVersionId が含まれる場合、この値で物理モジュールのビルドを区別します。また、Token は Operation のオペランドです。これらのフィールドを使って実行時情報と静的情報を結合するか、Evidence テキストを解析せずに、トリアージ行を対応するペアワイズの diff または diff --history による確認ワークフローへ引き継ぎます:

dnx dotnet-inspect -y -- library MyLib.dll -S "Performance:*" \
  --where "Finding=analysis.allocation" --where "Operation=box" --json
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance:*" \
  --where "Finding=analysis.call-site" --json

一致するパフォーマンスの証拠があり、レンダリングされた C# 構文種別を 1 つ含む、ソースに対応するメソッドを調べるには、Kind 条件を追加し、-S を省略します:

dnx dotnet-inspect -y -- library MyLib.dll \
  --where "Kind=InvocationExpression" \
  --where "Finding=analysis.call-site" \
  --where "Shape=sync-call-in-async" \
  --where "Confidence>=medium" --jsonl

これは Performance 行ではなく、Body Shapes を出力します。型付きのパフォーマンス機会は、逆コンパイル前にソースの MethodDef 本体を絞り込みます。候補、証拠、IL のレシートが必要な場合は、Performance クエリを別途実行してください。--top と --order-by は Body Shapes と併用できません。レンダリングする構文マッチを制限するには、--rows を使用してください。

allocation-hotspot などの集計行は Provenance=aggregate を使用し、pt~ 候補 ID を持ちますが、正確なソースの Finding、操作、トークンはありません。複合的な反復スキャンの判定では、それを裏付ける正確なローカル呼び出しを、SupportingFinding、SupportingOperation、SupportingToken、SupportingEvidenceMethod、SupportingIL に別途保持できます。これらのフィールドは実行時対応の座標であり、集計行のソース Finding や候補 ID ではありません。 Provenance=unmatched は、想定される生成元の全数調査と結合されなかった命令レベルの行を示します。

直接の呼び出し元ループによる反復を選択する

呼び出しごとに 1 回の割り当てでも、上流の呼び出し元にあるループによって反復される場合があります。正確な直接呼び出しレシートがある行を選択してください。

dnx dotnet-inspect -y -- library MyLib.dll -S "Performance:*" \
  --where "CallerLoop=direct" --json

CallerLoopDepth と CallerLoopWitness は、決定的な呼び出しサイトを示します。この証拠によって、行のローカルな Loop、多重度、信頼度、重み、候補 ID、デフォルトの順位が変わることはありません。プロファイリング対象の候補を選ぶために使用してください。呼び出し元が高負荷であることや、ループが実行されることの証明としては使用しないでください。

対象となるのは、解決済みの呼び出しエッジのみです。関数のロードやコールバックの登録は、コールバックが実行されることの証明にはなりません。また、再帰的な走査も、実際に到達した深さや頻度の証明にはなりません。どちらのケースも呼び出し元ループの証拠として推論せず、実行時の証拠、または製品が定めた、より強い呼び出し契約を必要としてください。

例外パスでのプールの過剰消費をトリアージする

明示的な Resource Triage ライブラリセクションを選択すると、正確な def-use パスが、モデル化されたクリーンアップより前に外部入力境界へ到達する ArrayPool<T> の取得を見つけられます。

dnx dotnet-inspect -y -- library MyLib.dll -S "Resource Triage" --jsonl

pool-churn-on-exception は、プロファイリングと堅牢化の候補として扱い、永続的なメモリリークやメモリ破損の告発として扱わないでください。静的解析で証明されるのは、保護されていない境界の形状と API の証拠であり、実行時の頻度ではありません。メソッドを詳しく調べる際は、Candidate、Finding=analysis.resource-lifecycle、Acquire IL、Boundary IL、Boundary を使用して、正確な出所情報を保持してください。各境界は 1 行で表されます。複数の境界を持つ候補では、各操作をそれぞれの IL オフセットと対応させるため、候補と取得のフィールドが繰り返されます。

すべてのパターンが、ホットパスを純粋に改善するとは限りません。割り当てがループ内にない場合、async-state-machine は償却済み(信頼度低)として報告されます。非同期の低レベル変換は処理をなくすのではなく、状態オブジェクトへ移すため、多くの場合、呼び出し、列挙、サブスクリプションごとに 1 回の割り当てが発生します。償却済みの行は背景情報として扱い、最適化する前に、実際に項目ごとのコストが発生していることをプロファイラーで確認してください。

割り当てがいつ発生したかを確認する

呼び出し元が選択したパッケージセル全体で、1 つのメソッドのネイティブ割り当ての全数調査を関連付けます。

dnx dotnet-inspect -y -- diff --history --package [email protected] \
  -t MyType -m HotPath \
  --finding analysis.allocation --at first --at last

疎なプローブには --at を繰り返し使用するか、明示的に上限を設けた密な走査には --at all を使用してください。これらのプローブで特定できるのは、候補となる旧版と新版の境界であり、発生時点を確定するものではありません。1 つのメソッドの隣接するバージョンの組み合わせは、Analysis のネイティブ割り当て Findings で確認してください。

dnx dotnet-inspect -y -- diff --package [email protected] \
  -t MyType -m HotPath \
  --finding analysis.allocation

メソッドの対象は、いずれか一方または両方の終点で解決される必要があります。PairFinding.Added は割り当ての発生が追加されたことを確認し、Present、Removed、Changed はそれぞれ、境界の誤り、消失、割り当ての特性の変化を示します。このコマンドはバージョンを走査しません。検索方針と上限は呼び出し元が管理します。

可能性の高い原因を新しい呼び出しまでたどる

割り当て境界を確認したら、同じ呼び出し元メソッドの直接呼び出しの件数を比較します。

dnx dotnet-inspect -y -- diff --package [email protected] \
  -t MyType -m HotPath \
  --finding analysis.call-site

対象メソッドは呼び出し元で、各行は呼び出し先を示します。 PairFinding.Added は、新たに導入された Enumerable.ToArray などの新しい呼び出しの発生を確認できます。Changed は、既存の呼び出しがループ内に移動した場合や、ディスパッチ/オペコードの要素が変化した場合に示されます。バージョンを調査する間は単一バージョンの Calls セクションを使用し、この最終的な隣接バージョンの比較を発生時点の証明に使用します。

候補を詳しく調べる

Call Graph は、選択したメンバーを中心として、エントリポイントまでの上限付きの双方向グラフを表示します。上流の呼び出し元と下流の呼び出しを含みます。--fields(alloc、copy、unsafe、reflection、throw/exception、catch/finally)を使って、ノードごとのコストを表示できます。デフォルトの Markdown エッジテーブルは、関係やコストの手掛かりを比較するのに最適です。候補までの経路、または候補から先への経路が重要な場合は --tree、単独の図には --mermaid、図を埋め込むには --markdown --mermaid を使用します。同じエッジ行をスクリプトで処理する場合は --tsv または --jsonl を使用します。指定したコストの手掛かりはノードラベル内の注釈として表示され、機械処理用の独立した列にはなりません。

Call Graph は --envelope に対応していません。エッジ行には --jsonl または --tsv を使用してください。ドキュメント化されている --json は、現在グラフのエッジではなく、周囲の Type 結果を出力します。完全一致のメンバーに対する --share url は、別個の公開 API 概要への投影であり、パフォーマンスや Call Graph の観点は保持されません。

dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll -S "Call Graph,Facts"
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll -S "Call Graph" --fields "Throw,Catch,Finally"
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll -S "Call Graph" --fields "Alloc,Loop" --tree
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll -S "Call Graph" --jsonl

コメント