build-perf-diagnostics について
バイナリログから MSBuild の性能を分析・改善するスキルです。ビルドの遅さに取り組む開発者向けに、binlog MCP の照会とログ再生による診断、一般的なボトルネックの分析手順をまとめています。
主な機能
- 01ビルド binlog の生成と確認
- 02ターゲットとタスクの所要時間分析
- 03ノード使用率とアナライザーの確認
- 04一般的なビルドボトルネックの特定
- 05影響度に基づく調査結果の優先順位付け
活用シーン
- 01.NET ビルドで遅いターゲットを特定
- 02並列ビルドの使用率低下を調査
- 03アナライザーやコピータスクの負荷を評価
- 04ビルド性能改善の優先順位を決定
MCP 対応
`binlog` 名前空間で提供される同梱の `Microsoft.AITools.BinlogMcp` サーバーを性能分析に使用します。MCP が利用できない場合に binlog を診断用テキストログへ再生する手順も記載されています。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dotnet/ skills --skill build-perf-diagnosticsインストール後、Claude Code で/build-perf-diagnosticsを入力して読み込まれたか確認します。
詳細
パフォーマンス分析の方法
- binlog を生成する:
dotnet build /bl:{} -m - このプラグインにバンドルされている binlog MCP サーバー(
binlogMCP 名前空間で公開されるMicrosoft.AITools.BinlogMcp)を使用します
サーバーに接続できず、起動時の修復を行う場合は、troubleshoot-binlog-mcp を使用します。修復がブロックされた場合、拒否された場合、または不要な場合は、以下のリプレイ手順を使用します。
MCP を利用できない場合の代替手順:binlog をテキストログにリプレイする
- binlog を生成する:
dotnet build /bl:{} -m - パフォーマンスの概要を含む診断ログにリプレイする:
dotnet msbuild build.binlog -noconlog -fl "-flp:v=diag;logfile=full.log;performancesummary" - パフォーマンスの概要を確認する(
full.logの末尾):grep "Target Performance Summary\|Task Performance Summary" -A 50 full.log - 時間のかかるターゲットとタスクを特定する:PerformanceSummary セクションには、累積時間の長い順にすべてのターゲット/タスクが表示されます
- ノードの使用率を確認する:スケジューリングとノードに関するメッセージを grep します
grep -i "node.*assigned\|building with\|scheduler" full.log | head -30 - アナライザーを確認する:アナライザーの実行時間を grep します
grep -i "analyzer.*elapsed\|Total analyzer execution time\|CompilerAnalyzerDriver" full.log
主なメトリクスとしきい値
- ビルド時間:「通常」の目安は、小規模なプロジェクトで <10s、中規模で <60s、大規模で <5min です
- ノードの使用率:ノード全体のアクティブ時間が >80% であることが理想です。使用率が低い場合は、直列化がボトルネックになっています
- 単一ターゲットの占有率:1 つのターゲットがビルド時間の >50% を占める場合は、調査してください
- アナライザー時間とコンパイル時間の比較:アナライザーの実行時間は Csc タスク時間の <30% に抑える必要があります。これを超える場合は、負荷の高いアナライザーの削除を検討してください
- RAR 時間:ResolveAssemblyReference が >5s かかる場合は注意が必要です。>15s は異常な状態です
よくあるボトルネック
1. ResolveAssemblyReference(RAR)の遅延
- 症状:プロジェクトごとに RAR に >5s かかる
- 根本原因:アセンブリ参照が多すぎる、ネットワーク上の参照パス、大規模なアセンブリ検索パス
- 対処方法:参照数を減らす、RAR に負荷の高い分析では
<DesignTimeBuild>false</DesignTimeBuild>を使用する、診断のために<ResolveAssemblyReferencesSilent>true</ResolveAssemblyReferencesSilent>を設定する - 高度な設定:
<DesignTimeBuild>と<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch> - 重要なポイント:ユーザーがターゲットパックをインストールしたり、アセンブリを GAC に登録したりしている可能性があるため、増分ビルドでも RAR は無条件に実行されます(dotnet/msbuild#2015 を参照)。.NET Core のマイクロアセンブリでは、参照数が非常に多くなることがよくあります。
- 推移的な参照を減らす:
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>を設定して、推移的参照の完全な閉包が取り込まれないようにします(注:使用する型への直接参照をプロジェクトに追加する必要がある場合があります)。ビルド時にのみ必要で、API サーフェスには含めない ProjectReferences にはReferenceOutputAssembly="false"を使用します。未使用の PackageReferences を削除します。
2. Roslyn アナライザーとソースジェネレーター
- 症状:ファイル数に対して Csc タスクの実行時間が予想より大幅に長い(クリーンコンパイル時間の >2×)
- 診断:リプレイしたログの Task Performance Summary で Csc タスクの実行時間を確認します。アナライザーの実行時間に関するメッセージを grep します。アナライザーの有効時と無効時の Csc の実行時間を比較します(
/p:RunAnalyzers=false) - 対処方法:
- 開発時に条件付きで無効化する:
<RunAnalyzers Condition="'$(ContinuousIntegrationBuild)' != 'true'">false</RunAnalyzers> - 構成ごとに設定する:
<RunAnalyzers Condition="'$(Configuration)' == 'Debug'">false</RunAnalyzers> - コードスタイルのみを対象にする:
<EnforceCodeStyleInBuild Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</EnforceCodeStyleInBuild> - 開発時の反復処理で本当に不要なアナライザーを削除する
- 重要度の低いルールは .editorconfig で重大度を設定する
- 開発時に条件付きで無効化する:
- 基本原則:CI でのアナライザー適用は維持します。アナライザーを単に「削除」してはいけません。条件付きで設定してください。
- GlobalPackageReference:
Directory.Packages.propsのGlobalPackageReferenceで追加したアナライザーは、すべてのプロジェクトに適用されます。テストプロジェクトにも本番コードと同じアナライザー一式が必要か検討してください。 - EnforceCodeStyleInBuild:
Directory.Build.propsでtrueに設定すると、すべてのビルドでコードスタイル分析が強制されます。開発時の反復処理が遅くならないよう、CI 環境(ContinuousIntegrationBuild)に応じた条件を設定してください。
3. シリアル化によるボトルネック(シングルスレッドのターゲット)
- 症状:パフォーマンスの概要では、ビルド時間の大半が単一のプロジェクトに集中しています。診断ログでは、1 つのノードが処理中の間、ほかのノードがアイドル状態になっています。
- よくある原因:適切な依存関係が宣言されていないターゲット、クリティカルパス上にある単一のプロジェクト
- 対処法:大規模なプロジェクトを分割し、クリティカルパス上のプロジェクトを最適化して、
BuildInParallelが適切に設定されていることを確認します。
4. 過剰なファイル I/O(Copy タスク)
- 症状:Copy タスクの累積時間が長くなっています。
- 根本原因:数千のファイルをコピーしている、ネットワークドライブ間でコピーしている、Copy タスクが単一のバッチとしてではなく、項目ごと(ファイルごと)に意図せず実行されている(dotnet/msbuild#12884 を参照)
- 対処法:ハードリンク(
<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>)を使用し、CopyToOutputDirectory の項目を減らし、適切な場合は<UseCommonOutputDirectory>true</UseCommonOutputDirectory>を使用し、<SkipCopyUnchangedFiles>true</SkipCopyUnchangedFiles>を設定します。また、出力レイアウトを一元化するために--artifacts-path(.NET 8 以降)の使用を検討します。 - Dev Drive:Windows では、Dev Drive(コピーオンライトと Defender スキャンの削減機能を備えた ReFS)に切り替えることで、Copy が多いビルドのファイル I/O オーバーヘッドを大幅に削減できます。開発マシンとセルフホスト型 CI エージェントの両方での使用を推奨します。
5. 評価のオーバーヘッド
- 症状:コンパイルが始まる前のビルド開始が遅くなっています。
- 根本原因:複雑な Directory.Build.props、大規模なディレクトリを走査するワイルドカードグロブ、NuGetSdkResolver のオーバーヘッド(復元済みの場合でもプロジェクト評価ごとに 180~400 ms 追加されます。dotnet/msbuild#4025 を参照)
- 対処法:Directory.Build.props の複雑さを軽減し、ファイルリストを明示しているレガシープロジェクトでは
<EnableDefaultItems>false</EnableDefaultItems>を使用します。可能であれば、NuGet ベースの SDK リゾルバーを避けます。 - 詳細なガイダンスについては、
eval-performanceスキルを参照してください。
6. ビルド時の NuGet 復元
- 症状:不要な場合でも、ビルドのたびに復元が実行されます。
- 対処法:
- 復元とビルドを分けます:
dotnet restoreを実行してからdotnet build --no-restoreを実行します。 - Directory.Build.props で
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>を設定して静的グラフ評価を有効にします。大規模なビルドでは大幅な時間短縮が期待できます(結果はワークロードによって異なります)。
- 復元とビルドを分けます:
- パイプラインまたはスクリプトの複数のステップでそれぞれ復元が実行されている場合、または
--no-restoreを追加する前に、eliminate-redundant-restoresスキルを参照してください(前の復元がそのステップをカバーしていない場合、NETSDK1004/NETSDK1047 で失敗します)。
7. プロジェクト数とグラフ構造の増大
- 症状:小規模なプロジェクトが多数あり、各プロジェクトの処理時間はごくわずかでも、オーバーヘッドが積み重なっています。依存関係チェーンが深く、ビルドが直列化されています。
- 検討事項:プロジェクトの統合、またはスケジューリングを改善するための
/graphモードの使用 - グラフ構造が重要です:幅の広い依存関係グラフ(階層が少なく、並列分岐が多い)は、深いグラフ(階層が多く、直列化される)よりも高速にビルドできます。深い構造から幅の広い構造にリファクタリングすると、クリーンビルドとインクリメンタルビルドの両方で大幅な改善が見込めます。
- 対応策:不要なプロジェクト依存関係を探し、ボトルネックとなっているプロジェクトを 2 つに分割するか、小規模なリーフプロジェクトを統合することを検討します。
パフォーマンス分析における Binlog リプレイの使用
テキストログのリプレイを使用する手順:
- パフォーマンスの概要を表示してリプレイする:
dotnet msbuild build.binlog -noconlog -fl "-flp:v=diag;logfile=full.log;performancesummary" - ターゲット/タスクのパフォーマンス概要を読む(
full.logの末尾):
これにより、累積時間の長い順にすべてのターゲットとタスクが表示されます。時間のかかるターゲット/タスクを特定するのと同じ方法です。grep "Target Performance Summary\|Task Performance Summary" -A 50 full.log - プロジェクトごとのビルド時間を確認する:
grep "done building project\|Project Performance Summary" full.log - 並列処理を確認する(マルチノードのスケジューリング):
grep -i "node.*assigned\|RequiresLeadingNewline\|Building with" full.log | head -30 - アナライザーのオーバーヘッドを確認する:
grep -i "Total analyzer execution time\|analyzer.*elapsed\|CompilerAnalyzerDriver" full.log - 特定の低速なターゲットを詳しく調べる:
grep 'Target "CoreCompile"\|Target "ResolveAssemblyReferences"' full.log
すぐにできる改善チェックリスト
-
/maxcpucount(または-m)を使用して並列ビルドを行います - 復元とビルドを分けます(
dotnet restoreの後にdotnet build --no-restoreを実行します) - 静的グラフによる復元を有効にします(
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>) - コピー時のハードリンクを有効にします(
<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>) - 開発時のインナーループでは、条件付きでアナライザーを無効にします:
<RunAnalyzers Condition="'$(ContinuousIntegrationBuild)' != 'true'">false</RunAnalyzers> - 参照アセンブリを有効にします(
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>) - 増分ビルドが壊れていないか確認します(
incremental-buildスキルを参照) - bin/obj の競合がないか確認します(
check-bin-obj-clashスキルを参照) - 複数プロジェクトのソリューションでは、グラフビルド(
/graph)を使用します - 出力先のレイアウトを一元化するには、
.NET 8+の--artifacts-pathを使用します - Windows の開発マシンとセルフホスト CI で Dev Drive(ReFS)を有効にします
影響度の分類
調査結果を報告する際は、修正の優先順位を付けやすいよう、影響度で分類します。
- 🔴 影響大(最初に対応):ビルド時間全体の 10% を超えて消費する項目、またはビルド時間の 50% を超える単一のターゲット
- 🟡 影響中:ビルド時間の 2~10% を消費する項目
- 🟢 すぐにできる改善:影響は小さくても簡単に変更できる項目(例:Directory.Build.props のプロパティフラグ)
コメント
Markdown 対応