Build Perf Diagnostics
.NET (Microsoft)OfficialPublished by .NET (Microsoft)
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured methodology for identifying and mitigating slow build times in MSBuild projects. It focuses on analyzing binlog data to uncover inefficiencies such as excessive ResolveAssemblyReference (RAR) times, high analyzer overhead, serialization bottlenecks, and file I/O issues. By leveraging performance summaries and diagnostic logs, developers can pinpoint specific targets or tasks consuming disproportionate build time and apply targeted configuration fixes to improve build speed.
What it does
- Analyzes MSBuild binlogs to identify performance bottlenecks.
- Provides commands to replay binlogs into diagnostic text logs with performance summaries.
- Offers specific guidance for resolving slow ResolveAssemblyReference (RAR) tasks.
- Details strategies to optimize Roslyn analyzer and source generator execution.
- Suggests configuration changes to reduce file I/O overhead and improve node utilization.
- Categorizes performance findings by impact to prioritize optimization efforts.
When to use it
- When experiencing unexplained build slowness.
- When ResolveAssemblyReference (RAR) tasks exceed 5 seconds.
- When analyzers consume more than 30% of Csc task time.
- When a single target accounts for over 50% of total build time.
- When node utilization remains consistently below 80% during builds.
- When excessive Copy tasks or restore overhead are suspected.
Advantages
- Data-driven approach to build performance tuning.
- Provides clear thresholds for identifying pathological build behavior.
- Includes actionable configuration fixes for common MSBuild issues.
- Helps distinguish between dev-loop optimizations and CI requirements.
- Offers a systematic checklist for quick performance wins.
Usage examples
- “How do I generate a performance summary for my build?”
- The agent provides the command to generate a binlog using 'dotnet build /bl' and then replay it with '-flp:v=diag;performancesummary' to extract the Target and Task Performance Summaries.
- “My build is slow because of analyzers, what should I do?”
- The agent suggests conditionally disabling analyzers in the dev inner loop using the 'RunAnalyzers' property while ensuring they remain active for CI builds.
- “How can I reduce file I/O overhead during my build?”
- The agent recommends enabling hardlinks for Copy tasks and using 'SkipCopyUnchangedFiles' or 'UseCommonOutputDirectory' to minimize redundant file operations.
- “What is a good way to check if my build is properly parallelized?”
- The agent instructs you to grep the diagnostic log for node assignment and scheduling messages to verify if nodes are being utilized effectively or if the build is serializing.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/dotnet/build-perf-diagnostics/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip build-perf-diagnostics.zip -d .agents/skills
Documentation by .NET (Microsoft)
Performance Analysis Methodology
Generate a binlog:
dotnet build /bl:{} -mUse the binlog MCP server (
Microsoft.AITools.BinlogMcp, exposed under thebinlogMCP namespace) which is bundled with this plugin
If the server cannot connect, use troubleshoot-binlog-mcp when startup repair is
wanted. If repair is blocked, declined, or unnecessary, use the replay flow below.
Alternate flow when MCP is unavailable: binlog replay to text logs
Generate a binlog:
dotnet build /bl:{} -mReplay to diagnostic log with performance summary:
dotnet msbuild build.binlog -noconlog -fl "-flp:v=diag;logfile=full.log;performancesummary"Read the performance summary (at the end of
full.log):grep "Target Performance Summary\|Task Performance Summary" -A 50 full.logFind expensive targets and tasks: The PerformanceSummary section lists all targets/tasks sorted by cumulative time
Check for node utilization: grep for scheduling and node messages
grep -i "node.*assigned\|building with\|scheduler" full.log | head -30Check analyzers: grep for analyzer timing
grep -i "analyzer.*elapsed\|Total analyzer execution time\|CompilerAnalyzerDriver" full.log
Key Metrics and Thresholds
Build duration: what's "normal" — small project <10s, medium <60s, large <5min
Node utilization: ideal is >80% active time across nodes. Low utilization = serialization bottleneck
Single target domination: if one target is >50% of build time, investigate
Analyzer time vs compile time: analyzers should be <30% of Csc task time. If higher, consider removing expensive analyzers
RAR time: ResolveAssemblyReference >5s is concerning. >15s is pathological
Common Bottlenecks
1. ResolveAssemblyReference (RAR) Slowness
Symptoms: RAR taking >5s per project
Root causes: too many assembly references, network-based reference paths, large assembly search paths
Fixes: reduce reference count, use
<DesignTimeBuild>false</DesignTimeBuild>for RAR-heavy analysis, set<ResolveAssemblyReferencesSilent>true</ResolveAssemblyReferencesSilent>for diagnosticAdvanced:
<DesignTimeBuild>and<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>Key insight: RAR runs unconditionally even on incremental builds because users may have installed targeting packs or GACed assemblies (see dotnet/msbuild#2015). With .NET Core micro-assemblies, the reference count is often very high.
Reduce transitive references: Set
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>to avoid pulling in the full transitive closure (note: projects may need to add direct references for any types they consume). UseReferenceOutputAssembly="false"on ProjectReferences that are only needed at build time (not API surface). Trim unused PackageReferences.
2. Roslyn Analyzers and Source Generators
Symptoms: Csc task takes much longer than expected for file count (>2× clean compile time)
Diagnosis: Check the Task Performance Summary in the replayed log for Csc task time; grep for analyzer timing messages; compare Csc duration with and without analyzers (
/p:RunAnalyzers=false)Fixes:
Conditionally disable in dev:
<RunAnalyzers Condition="'$(ContinuousIntegrationBuild)' != 'true'">false</RunAnalyzers>Per-configuration:
<RunAnalyzers Condition="'$(Configuration)' == 'Debug'">false</RunAnalyzers>Code-style only:
<EnforceCodeStyleInBuild Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</EnforceCodeStyleInBuild>Remove genuinely redundant analyzers from inner loop
Severity config in .editorconfig for less critical rules
Key principle: Preserve analyzer enforcement in CI. Never just "remove" analyzers — configure them conditionally.
GlobalPackageReference: Analyzers added via
GlobalPackageReferenceinDirectory.Packages.propsapply to ALL projects. Consider if test projects need the same analyzer set as production code.EnforceCodeStyleInBuild: When set to
trueinDirectory.Build.props, forces code-style analysis on every build. Should be conditional on CI environment (ContinuousIntegrationBuild) to avoid slowing dev inner loop.
3. Serialization Bottlenecks (Single-threaded targets)
Symptoms: Performance summary shows most build time concentrated in a single project; diagnostic log shows idle nodes while one works
Common culprits: targets without proper dependency declaration, single project on critical path
Fixes: split large projects, optimize the critical path project, ensure proper
BuildInParallel
4. Excessive File I/O (Copy tasks)
Symptoms: Copy task shows high aggregate time
Root causes: copying thousands of files, copying across network drives, Copy task unintentionally running once per item (per-file) instead of as a single batch (see dotnet/msbuild#12884)
Fixes: use hardlinks (
<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>), reduce CopyToOutputDirectory items, use<UseCommonOutputDirectory>true</UseCommonOutputDirectory>when appropriate, set<SkipCopyUnchangedFiles>true</SkipCopyUnchangedFiles>, consider--artifacts-path(.NET 8+) for centralized output layoutDev Drive: On Windows, switching to a Dev Drive (ReFS with copy-on-write and reduced Defender scans) can significantly reduce file I/O overhead for Copy-heavy builds. Recommend for both dev machines and self-hosted CI agents.
5. Evaluation Overhead
Symptoms: build starts slow before any compilation
Root causes: complex Directory.Build.props, wildcard globs scanning large directories, NuGetSdkResolver overhead (adds 180-400ms per project evaluation even when restored — see dotnet/msbuild#4025)
Fixes: reduce Directory.Build.props complexity, use
<EnableDefaultItems>false</EnableDefaultItems>for legacy projects with explicit file lists, avoid NuGet-based SDK resolvers if possibleSee:
eval-performanceskill for detailed guidance
6. NuGet Restore in Build
Symptoms: restore runs every build even when unnecessary
Fixes:
Separate restore from build:
dotnet restorethendotnet build --no-restoreEnable static graph evaluation:
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>in Directory.Build.props — can save significant time in large builds (results are workload-dependent)
See:
eliminate-redundant-restoresskill when several pipeline or script steps each restore, or before adding--no-restore(it fails with NETSDK1004/NETSDK1047 when no earlier restore covers the step)
7. Large Project Count and Graph Shape
Symptoms: many small projects, each takes minimal time but overhead adds up; deep dependency chains serialize the build
Consider: project consolidation, or use
/graphmode for better schedulingGraph shape matters: a wide dependency graph (few levels, many parallel branches) builds faster than a deep one (many levels, serialized). Refactoring from deep to wide can yield significant improvements in both clean and incremental build times.
Actions: look for unnecessary project dependencies, consider splitting a bottleneck project into two, or merging small leaf projects
Using Binlog Replay for Performance Analysis
Step-by-step workflow using text log replay:
Replay with performance summary:
dotnet msbuild build.binlog -noconlog -fl "-flp:v=diag;logfile=full.log;performancesummary"Read target/task performance summaries (at the end of
full.log):grep "Target Performance Summary\|Task Performance Summary" -A 50 full.logThis shows all targets and tasks sorted by cumulative time — equivalent to finding expensive targets/tasks.
Find per-project build times:
grep "done building project\|Project Performance Summary" full.logCheck parallelism (multi-node scheduling):
grep -i "node.*assigned\|RequiresLeadingNewline\|Building with" full.log | head -30Check analyzer overhead:
grep -i "Total analyzer execution time\|analyzer.*elapsed\|CompilerAnalyzerDriver" full.logDrill into a specific slow target:
grep 'Target "CoreCompile"\|Target "ResolveAssemblyReferences"' full.log
Quick Wins Checklist
[ ] Use
/maxcpucount(or-m) for parallel builds[ ] Separate restore from build (
dotnet restorethendotnet build --no-restore)[ ] Enable static graph restore (
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>)[ ] Enable hardlinks for Copy (
<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>)[ ] Disable analyzers conditionally in dev inner loop:
<RunAnalyzers Condition="'$(ContinuousIntegrationBuild)' != 'true'">false</RunAnalyzers>[ ] Enable reference assemblies (
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>)[ ] Check for broken incremental builds (see
incremental-buildskill)[ ] Check for bin/obj clashes (see
check-bin-obj-clashskill)[ ] Use graph build (
/graph) for multi-project solutions[ ] Use
--artifacts-path(.NET 8+) for centralized output layout[ ] Enable Dev Drive (ReFS) on Windows dev machines and self-hosted CI
Impact Categorization
When reporting findings, categorize by impact to help prioritize fixes:
🔴 HIGH IMPACT (do first): Items consuming >10% of total build time, or a single target >50% of build time
🟡 MEDIUM IMPACT: Items consuming 2-10% of build time
🟢 QUICK WINS: Easy changes with modest impact (e.g., property flags in Directory.Build.props)
Files
- ATTRIBUTION.mdAdded by AutoGineer858 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md10.3 KiB
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an MCP server, Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- .NET (Microsoft)
- Publisher
- .NET (Microsoft) (Official)
- Source
- dotnet/skills · plugins/dotnet-msbuild/skills/build-perf-diagnostics · commit 96c3d68
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by .NET (Microsoft).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download build-perf-diagnostics.zip