find-repeated-work-within-one-build について
このスキルは、単一の MSBuild ビルド内にある重複作業の発見を支援します。実行回数と binlog の証拠から、削減可能なタスクとプロジェクト、フレームワーク、項目ごとに必要な処理を区別し、動作を保つ修正を提案します。
主な機能
- 01binlog の実行回数分析
- 02重複作業と必要な処理の分類
- 03重複したプロジェクトインスタンスの特定
- 04削減可能なタスク時間の推定
- 05ビルド動作とインクリメンタル性の維持
- 06新しい binlog による修正の検証
活用シーン
- 01複数プロジェクトでのバージョン確認重複の調査
- 02グローバルプロパティによる再ビルドの診断
- 03項目ごとのプロセス起動の削減
- 04最適化を行う価値の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill find-repeated-work-within-one-buildインストール後、Claude Code で/find-repeated-work-within-one-buildを入力して読み込まれたか確認します。
詳細
1 回のビルド内で繰り返される処理を見つける
MSBuild は、プロジェクト インスタンスごとにターゲットを 1 回実行します。プロジェクトに依存しない処理が Directory.Build.targets や共有ターゲットに含まれていると、プロジェクトごとに 1 回実行されます。たとえば、40 個のプロジェクトで git rev-parse を実行すると、プロセスが 40 回起動します。グローバル プロパティが異なる条件でビルドされたプロジェクトは、別のインスタンスとなるため、共有ライブラリが 1 回のビルドで複数回コンパイルされることがあります。バイナリログの「N calls」列には、どちらのケースも表示されます。このスキルでは、繰り返される処理を見つけ、プロジェクトごとに実行する必要がある処理と区別し、安全に削除できる処理だけを取り除きます。
使用する場面
- 同じ
Execまたはタスク(バージョンのスタンプ付け、ツールの検出、ファイル生成)がすべてのプロジェクトで実行される - Target または Task Performance Summary の呼び出し回数が、その処理を必要とするプロジェクト数を大幅に上回る
- グローバル プロパティのセットを変えても出力が変わらないのに、同じプロジェクトのビルド ターゲットが繰り返し実行される
Execがリスト内の項目ごとにプロセスを起動する- 「ビルドごとに 1 回だけ実行する」ためのガード(
Existsチェックやフラグ プロパティ)が繰り返し実行される、または-mでファイル使用中エラーが発生する
使用しない場面
- 変更のない 2 回目のビルドでもターゲットが再実行される場合:
incremental-buildを使用してください - ターゲットが実行される前の評価が遅い場合:
eval-performanceを使用してください - 必要なプロジェクトのバリアントが共有の bin/obj ディレクトリで競合する場合:
check-bin-obj-clashを使用してください - ノードがアイドル状態になる場合、または依存関係の連鎖が直列になっている場合:
build-parallelismを使用してください - 別々の
dotnet restore、dotnet build、dotnet testコマンド間で NuGet の復元が繰り返される場合:eliminate-redundant-restoresを使用してください - 原因を絞らずに「ビルドが遅い理由」を調べる場合:まず
build-perf-baselineを使用し、次にbuild-perf-diagnosticsを使用してください
ワークフロー
ステップ 1:実行回数を数える
実行回数が記載されたパフォーマンスサマリーが提供されている場合は、有効な入力として使用できます。生の binlog は必要ありません。ユーザーがビルドを実行できない場合、または実行や新たなログの取得を禁止している場合は、サマリーとリポジトリのファイルを基に作業してください。取得できないタイミングや重複の証拠は unknown、提案する修正は not validated とし、ビルドを実行したり、別のログを取得したりしないでください。
使用可能な実行回数も binlog も提供されていない場合は、ビルドの実行が可能で許可されているときに限り、ログを取得してください。binlog-generation スキルを使用し、元の MSBuild 呼び出しに /bl:{} を追加して、構成、グローバルプロパティ、並列実行、復元の動作を維持してください。PowerShell では、スイッチ全体を '-bl:{}' と引用符で囲んでください。スクリプトがビルドを実行する場合は、別のビルドコマンドに置き換えるのではなく、スクリプト内の MSBuild 呼び出しに計測を追加してください。binlog が存在することを確認し、そのパスを記録してください。取得が許可されていない場合は、ビルドを実行せず、不足している証拠を報告してください。
binlog を利用できる場合は、バンドルされた binlog MCP サーバー(binlog MCP 名前空間で公開されている Microsoft.AITools.BinlogMcp)を優先してください。利用可能なツールを確認し、その binlog からプロジェクト、ターゲット、タスクの実行回数と所要時間を照会してください。バイナリファイルをテキストとして読み込まないでください。
MCP を利用できない場合、またはそのツールで実行回数を取得できない場合は、取得した binlog をテキスト形式のパフォーマンスサマリーに変換し、Target and Task Performance Summaries と Project Performance Summary の N calls 列を確認してください。
dotnet msbuild build.binlog -noconlog "-flp:v=diag;logfile=full.log;performancesummary"
利用側の数だけではなく、必要な個別の計算を数えてください。作業の種類ごとに期待される回数を別々に導き出してください。通常、Csc はプロジェクトとターゲットフレームワークの組み合わせごとに 1 回実行されますが、コピー ローカルの処理はファイル数に応じて増え、複数のターゲットで実行されることがあります。プロジェクト数と TFM 数の積は、すべてのタスクに共通する上限ではありません。より大きな回数が冗長だと主張せず、根拠がない期待回数は unknown としてください。マルチターゲットのプロジェクトでは、アウタービルド 1 回に加えて TFM ごとにインナービルドが 1 回実行されるのが正当な場合があります。そのため、TFM が 3 つのプロジェクトで呼び出しが 4 回あっても、自動的に重複とはなりません。余分な実行を調査する前に、必要な TFM とグローバルプロパティを考慮してください。
不変のツールバージョンまたはリビジョンを調べる処理は、複数のプロジェクトで結果を利用する場合でも、同等の入力スコープごとに 1 回の計算が必要です。この処理を一部のプロジェクトだけに制限するターゲット条件があっても、結果がプロジェクトごとに必要になるわけではありません。入力、作業ディレクトリ、関連するプロパティ、出力の意味が同等であることを確認してください。同じコマンドテキストであることだけでは、不変性の証明になりません。特に、プロジェクトごとに新規生成される署名 ID は、意図的に非決定的です。プロジェクト間やビルド間でキャッシュ、再利用、共有してはいけません。契約を変更せずに異なる結果をすべて生成できることが文書化されたツールモードを除き、必要な実行はそれぞれ維持してください。
ステップ 2:各候補を分類する
| パターン | シグナル | 修正方法 | 避けること |
|---|---|---|---|
プロジェクトごとに不変の Exec またはタスク(コミットハッシュ、ツールのバージョン、生成ファイル) | 同等の入力に対し、複数の利用者が同じ決定的な結果を必要としている | プロジェクトごとのターゲットの外側で一度だけ計算します。ビルドスクリプトから -p:Name=value を渡すか、ビルド前のステップまたはオーケストレーターのターゲットを先に実行します | フラグ用プロパティや Exists(stamp) ガードを使うこと。プロパティはプロジェクトインスタンスごとに設定され、-m のワーカーノードは別プロセスであるため、ガードでは重複を防げず、競合が発生します |
| 同じプロジェクトが複数回ビルドされる | 1 つのプロジェクトパスに対し、異なるグローバルプロパティを指定した N 回の呼び出しがある(たとえば、MSBuild タスクで Properties="ComponentName=%(Component.Filename)" を指定している)。未使用のものも含め、グローバルプロパティを 1 つ追加するだけで、新しいインスタンスが作成されます | そのプロパティを渡すのをやめるか、共有 ProjectReference に GlobalPropertiesToRemove メタデータを追加します(MSBuild タスクでは RemoveProperties を指定します) | プロジェクトが実際に読み取るプロパティを削除すること |
項目ごとに 1 プロセス(%(Item.Identity) でバッチ処理された Exec) | Exec の呼び出し回数が項目数と一致する | ツールが対応していることを確認したうえで、リストまたはレスポンスファイルを受け取る 1 回の呼び出しにします。Inputs/Outputs は維持します(1 対 1 の変換では、MSBuild はターゲットに古くなった項目だけを渡します) | 統合時に Inputs/Outputs を削除すること |
| 正当なプロジェクトごと、TFM ごと、または項目ごとの処理 | 回数が必要なプロジェクト、フレームワーク、または出力項目の範囲と一致する | そのままにします | 無関係なタスクの種類に、単一の回数上限を適用すること |
ステップ 3:効果を見積もる
不変の計算を削減する場合、回避可能なタスクの合計時間を (calls - expected) x average ms で見積もります。項目ごとのバッチ処理では、必要な入力処理はなくなりません。すべての入力の処理コストと必要なメタデータグループを維持したうえで、(old process starts - new process starts) x startup ms と見積もります。把握できている場合は、リストやバッチ処理の追加オーバーヘッドも考慮します。起動コストが不明な場合は、タスクの平均時間全体を削減可能と見なすのではなく、その見積もりを不明としてください。
実時間の短縮幅は、実際の並列実行とクリティカルパスによって異なります。1 つのターゲット内の項目ごとの Exec 呼び出しをバッチ処理すると、1 つのノード上で順次実行されます。そのため、無関係なノードがビジーでも、回避できた起動時間がその台数で割られることはありません。削減した処理が実際に複数ノードで重複して実行される場合に限り、有効な並列度で割ってください。binlog のタイムラインを使って並列実行状況を確認し、根拠がない場合は実時間の短縮幅を不明としてください。クリティカルパス外の処理を削減しても、実時間は短縮されない場合があります。パフォーマンスのみを目的とする変更では、集計した上限でさえビルドの実時間の約 1% を下回るなら、変更する価値はないと報告してください。タイミング上の効果にかかわらず、正しさに関わる競合は修正が必要です。
コストを根拠に変更しないことを推奨する場合は、回避可能な合計時間の上限を示します。ビルドの経過時間が提示されている場合は、実測の実時間短縮幅ではなく、上限比率として 100 x bound / elapsed も示します。技術的には重複しているものの、変更するコストに見合わない処理と、別個に維持する必要がある処理を区別してください。
ステップ 4:動作を変更せずに修正する
- 最初の使用箇所より前に値を利用できるようにします。
- 共有値がないビルドに備えて、
Condition="'$(X)' == ''"のフォールバックを維持します。インポートされたプロパティや環境プロパティでも条件を満たせるため、値をグローバルにする必要はありません。子ビルドは、削除されない限り既存のグローバルプロパティを継承しますが、親で計算された通常のプロパティは継承しません。親で計算した結果をMSBuildタスクのProperties経由で渡すか、フォールバックより前に各子で共有値を読み込みます。そうしないと、各子がそれぞれフォールバックを実行する可能性があります。 - 計算を一箇所に定義します。
- 内容が変わらない場合は、生成ファイルのタイムスタンプを維持します。差分がある場合のみ書き込むジェネレーターを使うか、一時ファイルに生成して、内容が変わった場合のみ置き換えます。重複排除によって、変更のない再実行時に依存先の再コンパイルが発生しないようにしてください。
<Target Name="StampCommit" BeforeTargets="CoreCompile">
<Exec Command="git rev-parse HEAD" ConsoleToMSBuild="true">
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommit" />
</Exec>
</Target>
<Target Name="StampCommit" BeforeTargets="CoreCompile">
<Exec Command="git rev-parse HEAD" ConsoleToMSBuild="true" Condition="'$(GitCommit)' == ''">
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommit" />
</Exec>
</Target>
Source Link がすでに SourceRevisionId を提供している場合、使用箇所より前に利用でき、既存のスタンプ表現を維持できる場合に限り、Exec を置き換えられます。通常、SourceRevisionId には完全なコミット ID が含まれます。元の省略形式の契約を維持せずに、git rev-parse --short HEAD の代わりとして使用しないでください。
ステップ 5:検証し、事実に即して報告する
- 実行が可能で許可されている場合は、同じ呼び出しの新しい binlog を取得します。呼び出し回数が想定数まで減っている必要があります。それ以外の場合は、ビルドを実行したり別のログを収集したりせず、修正案として提示し、未検証であることを報告してください。
- 出力(スタンプ値、生成ファイルの内容)が変わっていないことを確認します。生成ソースについては、2 回目のビルドでも変更がなければインクリメンタルなビルドになることも確認してください。
- 実行時間は少なくとも 3 回の実行の中央値として報告します。ビルドを実行できなかった場合は、「未検証」と記載してください。
出力
対象に関するリクエストでは、証拠に応じた簡潔な回答にしてください。少なくとも 1 行を含む簡潔な調査結果の表、決定的な計算または維持すべき制約についての短い説明、単独で書式設定されていない Verdict: 行を含めます。この行を太字にしたり、段落に続けて記載したりしないでください。利用できない時間やクリティカルパスの証拠には unknown を使用し、タスク時間の推定合計と実測した経過時間の短縮を区別してください。契約上、すべての実行が必要な場合、タスク時間が不明でも回避可能時間はゼロと報告してください。
| 作業 | 呼び出し回数 | 想定数 | 回避可能な ms | 原因 | 修正 | クリティカルパス上か |
|---|
検証後:Verdict: removed N repeated executions, measured wall-clock saving X s
未検証の場合:Verdict: proposed removal of N repeated executions; estimated task-time saving X s; wall-clock not validated
変更に価値がない場合:Verdict: no repeated work worth changing
よくある落とし穴
| 落とし穴 | 解決策 |
|---|---|
Csc や copy-local の回数を無駄とみなす | Csc はプロジェクト数と TFM 数の積と比較し、コピーは必要なファイル数とターゲット数に照らして確認します。どちらも同じ想定数だと決めつけないでください |
Exists(file) やフラグプロパティでガードする | 作業をプロジェクトごとのパスから移動します |
| 親のローカルプロパティが子ビルドに継承されると思い込む | 共有値を明示的に渡すか、フォールバックより前に各子で読み込みます。そうしないと、子ごとにフォールバックが繰り返されます。既存のグローバルプロパティは、削除されない限りすでに継承されています。 |
| タスク時間の合計から短縮を主張する | 実際の並列実行とクリティカルパスを確認します。逐次処理の時間を、無関係なビジーノードで割らないでください |
項目ごとの Exec をまとめ、インクリメンタル性を損なう | ターゲットの Inputs と Outputs を維持します |