[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M699CMPAR5F3AETQ3TG46Z50":3,"skill-same:01M699CMPAR5F3AETQ3TG46Z50":73},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":35,"linkOnly":36,"markdownContent":37,"markdownBody":38,"frontMatter":39,"refName":40,"commitSha":41,"skillDirectory":42,"forksCount":43,"repositoryHtmlUrl":44,"repositoryHomepageUrl":6,"ownerHtmlUrl":45,"ownerBlogUrl":46,"ownerDescription":47,"updatedAt":48,"install":49,"resources":62},"01M699CMPAR5F3AETQ3TG46Z50","build-perf-baseline",null,"Measure repeatable cold\u002Fchanged-input\u002Fno-op MSBuild baselines for caching, output layout and graph tuning; not task diagnosis or non-MSBuild builds.","microsoft\u002Ftestfx","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M699C71CJ91B5WZ3AXG3GG2M",1059,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fbuild-perf-baseline\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],"build perf baseline","perf","baseline","build","msbuild","graph","tuning","task","caching","layout","diagnosis","input","cold","output","op","for","2026-11-10 10:12:30",false,"---\nname: build-perf-baseline\ndescription: \"Measure repeatable cold\u002Fchanged-input\u002Fno-op MSBuild baselines for caching, output layout and graph tuning; not task diagnosis or non-MSBuild builds.\"\nlicense: MIT\n---\n\n# Build Performance Baseline & Optimization\n\n## Scope and routing\n\nEstablish MSBuild\u002F.NET build performance baselines before optimizing. USE FOR: a .NET build or solution that is slow or has build-performance concerns; cold, warm, incremental, or no-op measurements; before\u002Fafter comparisons; CI build output caching; static graph build decisions; artifacts output; and dependency graph trimming. Start here before build-perf-diagnostics, incremental-build, or build-parallelism. DO NOT USE for webpack, npm, JavaScript bundlers, or other non-MSBuild build systems. For detailed target\u002Ftask\u002Fanalyzer bottleneck analysis after baselining, use build-perf-diagnostics.\n\n## Overview\n\nBefore optimizing a build, you need a **baseline**. Without measurements, optimization is guesswork. This skill covers how to establish baselines and apply systematic optimization techniques.\n\n**Related skills:**\n- `build-perf-diagnostics` — binlog-based bottleneck identification\n- `incremental-build` — Inputs\u002FOutputs and up-to-date checks\n- `build-parallelism` — parallel and graph build tuning\n- `eval-performance` — glob and import chain optimization\n\n---\n\n## Step 1: Establish a Performance Baseline\n\nMeasure three scenarios to understand where time is spent. Keep the SDK,\nconfiguration, machine, environment variables, restore state, and build command\nconsistent. Run each scenario at least three times, repeating its setup before\nevery measured sample, and report the median plus the observed range; a single\ntiming is not a baseline. Keep setup outside the timed interval. Save each\nsample's binlog under a unique name, outside generated output directories.\n\n### Cold Build (First Build)\n\nNo previous build output exists. Measures the full end-to-end time including restore, compilation, and all targets.\n\nBefore **every cold sample**, restore the same source state, run `dotnet clean`\nwith the measured configuration, and remove only the confirmed, disposable\noutput and intermediate directories for the measured projects. Include custom\nartifact paths, not just `bin` and `obj`, and obtain approval before deletion.\nVerify those outputs are absent before timing the next build.\n\nThis is an **output-cold** build, not necessarily a cold NuGet, OS, or compiler\nserver cache. Choose and record a consistent cache\u002Fserver policy for all\nsamples; do not clear shared caches. If measuring uncached restore, use a\nseparate, empty package cache for each sample.\n\n```shell\n# After repeating the cold setup; use a unique log name for each sample\ndotnet build \u002Fbl:cold-build-1.binlog -m\n```\n\n### Warm Build (Incremental Build)\n\nBuild output exists, some files have changed. Measures how well incremental build works.\n\nBefore **every warm sample**, restore the same baseline source contents and\nbuild successfully without timing it. Then apply the same small, build-relevant\nedit to the same source file and time the build. Keep the changed file set and\nedit identical across samples; do not let edits accumulate. Restore the\nbaseline contents before the next sample and rebuild them outside the timed\ninterval. Do not clean between that setup build and its measured build.\n\n```shell\n# Untimed setup after restoring baseline source contents\ndotnet build -m\n\n# Apply the same controlled source edit, then measure with a unique log name\ndotnet build \u002Fbl:warm-build-1.binlog -m\n```\n\n### No-Op Build (Nothing Changed)\n\nBuild output exists, nothing has changed. Compilation and correctly incremental\ntargets should skip; compare timing with this build's other samples.\n\nBefore **every no-op sample**, restore the same baseline source contents and\nrun an untimed setup build successfully. Then measure an identical build\nwithout edits, touching inputs, cleaning outputs, or changing properties.\nKeep restore and cache\u002Fserver policy consistent with the other samples.\n\n```shell\n# Untimed setup after restoring baseline source contents\ndotnet build -m\n\n# Rebuild immediately without changes; use a unique log name for each sample\ndotnet build \u002Fbl:noop-build-1.binlog -m\n```\n\n### What Good Looks Like\n\n| Scenario | Expected Behavior |\n|----------|------------------|\n| Cold build | Full compilation, all targets run. This is your absolute baseline |\n| Warm build | Only changed projects recompile. Time proportional to change scope |\n| No-op build | Compilation and correctly incremental custom targets skip; compare duration with this repo's repeated warm and cold samples |\n\n**Red flags:**\n- No-op time is repeatedly close to warm\u002Fcold time, or compilation targets rerun → investigate incrementality (see `incremental-build`)\n- Warm build recompiles everything → project dependency chain forces full rebuild\n- Restore dominates cold samples → measure `dotnet restore` and `dotnet build --no-restore` separately before changing project structure\n\nDo not use universal duration or percentage thresholds to declare a bottleneck.\nRank costs against the controlled samples and the build's own target\u002Ftask\ntimings.\n\n### Capture analyzer evidence\n\nA binlog shows compiler\u002Ftask timing, but granular analyzer timing requires an\nanalyzer-reporting run. When supported by the SDK\u002Fcompiler, capture:\n\n```shell\ndotnet build \u002Fbl:analyzers.binlog \u002Fp:ReportAnalyzer=true\n```\n\nOpen the binlog in MSBuild Structured Log Viewer and inspect the analyzer\nsummary under the compiler task. If granular timing is unavailable, compare\notherwise identical samples with `\u002Fp:RunAnalyzers=false` as an attribution\nexperiment; do not present disabling analyzers as the fix. Preserve analyzer\nenforcement in CI.\n\n### Recording Baselines\n\nRecord baselines in a structured way before and after optimization:\n\n```\n| Scenario    | Before  | After   | Improvement |\n|-------------|---------|---------|-------------|\n| Cold build  | 2m 15s  |         |             |\n| Warm build  | 1m 40s  |         |             |\n| No-op build | 45s     |         |             |\n```\n\n---\n\n## Step 2: Artifacts Output Layout\n\nThe `UseArtifactsOutput` feature (introduced in .NET 8) changes the output directory structure to avoid bin\u002Fobj clash issues and enable better caching.\n\n### Enabling Artifacts Output\n\n```xml\n\u003C!-- Directory.Build.props -->\n\u003CPropertyGroup>\n  \u003CUseArtifactsOutput>true\u003C\u002FUseArtifactsOutput>\n\u003C\u002FPropertyGroup>\n```\n\n### Before vs After\n\n```\n# Traditional layout (before)\nsrc\u002F\n  MyLib\u002F\n    bin\u002FDebug\u002Fnet8.0\u002FMyLib.dll\n    obj\u002FDebug\u002Fnet8.0\u002F...\n  MyApp\u002F\n    bin\u002FDebug\u002Fnet8.0\u002FMyApp.dll\n\n# Artifacts layout (after)\nartifacts\u002F\n  bin\u002FMyLib\u002Fdebug\u002FMyLib.dll\n  bin\u002FMyApp\u002Fdebug\u002FMyApp.dll\n  obj\u002FMyLib\u002Fdebug\u002F...\n  obj\u002FMyApp\u002Fdebug\u002F...\n```\n\n### Benefits\n\n- **No bin\u002Fobj clash**: Each project+configuration gets a unique path automatically\n- **Easier to cache**: Single `artifacts\u002F` directory to cache\u002Frestore in CI\n- **Cleaner .gitignore**: Just ignore `artifacts\u002F`\n- **Multi-targeting safe**: Each TFM gets its own subdirectory\n\n### Customizing\n\n```xml\n\u003C!-- Change the artifacts root -->\n\u003CPropertyGroup>\n  \u003CArtifactsPath>$(MSBuildThisFileDirectory)output\u003C\u002FArtifactsPath>\n\u003C\u002FPropertyGroup>\n```\n\n---\n\n## Step 3: Deterministic Builds\n\nDeterministic builds produce byte-for-byte identical output given the same inputs. This is essential for build caching and reproducibility.\n\n### Enabling Deterministic Builds\n\n```xml\n\u003C!-- Directory.Build.props -->\n\u003CPropertyGroup>\n  \u003C!-- Enabled by default in .NET SDK projects since SDK 2.0+ -->\n  \u003CDeterministic>true\u003C\u002FDeterministic>\n\n  \u003C!-- For full reproducibility, also set: -->\n  \u003CContinuousIntegrationBuild Condition=\"'$(CI)' == 'true'\">true\u003C\u002FContinuousIntegrationBuild>\n\u003C\u002FPropertyGroup>\n```\n\n### What Deterministic Affects\n\n- Removes timestamps from PE headers\n- Uses consistent file paths in PDBs\n- Produces identical output for identical input\n\n### Why It Matters for Performance\n\n- **Build caching**: If outputs are deterministic, you can cache and reuse them across builds and machines\n- **CI optimization**: Skip rebuilding unchanged projects by comparing inputs\n- **Distributed builds**: Safe to cache compilation results in shared storage\n\n---\n\n## Step 4: Dependency Graph Trimming\n\nReducing unnecessary project references shortens the critical path and reduces what gets built.\n\n### Audit the Dependency Graph\n\n```bash\n# Visualize the dependency graph\ndotnet build \u002Fbl:graph.binlog\n\n# In the binlog, check project references and build times\n# Look for projects that are referenced but could be trimmed\n```\n\n### Techniques\n\n#### Remove Redundant Transitive References\n\n```xml\n\u003C!-- BAD: Utils is already referenced transitively via Core -->\n\u003CItemGroup>\n  \u003CProjectReference Include=\"..\\Core\\Core.csproj\" \u002F>\n  \u003CProjectReference Include=\"..\\Utils\\Utils.csproj\" \u002F>\n\u003C\u002FItemGroup>\n\n\u003C!-- GOOD: Let transitive references flow automatically -->\n\u003CItemGroup>\n  \u003CProjectReference Include=\"..\\Core\\Core.csproj\" \u002F>\n\u003C\u002FItemGroup>\n```\n\n#### Build-Order-Only References\n\nWhen you need a project to build before yours but don't need its assembly output:\n\n```xml\n\u003C!-- Only ensures build order, doesn't reference the output assembly -->\n\u003CProjectReference Include=\"..\\CodeGen\\CodeGen.csproj\"\n                  ReferenceOutputAssembly=\"false\" \u002F>\n```\n\n#### Prevent Transitive Flow\n\nWhen a dependency is an internal implementation detail that shouldn't flow to consumers:\n\n```xml\n\u003C!-- Don't expose this dependency transitively -->\n\u003CProjectReference Include=\"..\\InternalHelpers\\InternalHelpers.csproj\"\n                  PrivateAssets=\"all\" \u002F>\n```\n\n#### Disable Transitive Project References\n\nFor explicit-only dependency management (extreme measure for very large repos):\n\n```xml\n\u003CPropertyGroup>\n  \u003CDisableTransitiveProjectReferences>true\u003C\u002FDisableTransitiveProjectReferences>\n\u003C\u002FPropertyGroup>\n```\n\n**Caution**: This requires all dependencies to be listed explicitly. Only use in large repos where transitive closure is causing excessive rebuilds.\n\n---\n\n## Step 5: Static Graph Builds (`\u002Fgraph`)\n\nStatic graph mode evaluates the entire project graph before building, enabling better scheduling and isolation.\n\n### Enabling Graph Build\n\n```bash\n# Single invocation\ndotnet build \u002Fgraph\n\n# With binary log for analysis\ndotnet build \u002Fgraph \u002Fbl:graph-build.binlog\n```\n\n### Benefits\n\n- **Better parallelism**: MSBuild knows the full graph upfront and can schedule optimally\n- **Build isolation**: Each project builds in isolation (no cross-project state leakage)\n- **Caching potential**: With isolation, individual project results can be cached\n\n### When to Use\n\n| Scenario | Recommendation |\n|----------|---------------|\n| Large multi-project solution (20+ projects) | ✅ Try `\u002Fgraph` — may see significant parallelism gains |\n| Small solution (\u003C 5 projects) | ❌ Overhead of graph evaluation outweighs benefits |\n| CI builds | ✅ Graph builds are more predictable and parallelizable |\n| Local development | ⚠️ Test both — may or may not help depending on project structure |\n\n### Troubleshooting Graph Build\n\nGraph build requires that all `ProjectReference` items are statically determinable (no dynamic references computed in targets). If graph build fails:\n\n```\nerror MSB4260: Project reference \"...\" could not be resolved with static graph.\n```\n\n**Fix**: Ensure all `ProjectReference` items are declared in `\u003CItemGroup>` outside of targets (not dynamically computed inside `\u003CTarget>` blocks).\n\n---\n\n## Step 6: Parallel Build Tuning\n\n### MaxCpuCount\n\n```bash\n# Use all available cores (default in dotnet build)\ndotnet build -m\n\n# Specify explicit core count (useful for CI with shared agents)\ndotnet build -m:4\n\n# MSBuild.exe syntax\nmsbuild \u002Fm:8 MySolution.sln\n```\n\n### Identifying Parallelism Bottlenecks\n\nIn a binlog, look for:\n- **Long sequential chains**: Projects that must build one after another due to dependencies\n- **Uneven load**: Some build nodes idle while others are overloaded\n- **Single-project bottleneck**: One large project on the critical path that blocks everything\n\nUse `grep 'Target Performance Summary' -A 30 full.log` in binlog analysis to see build node utilization.\n\n### Reducing the Critical Path\n\nThe critical path is the longest chain of dependent projects. To shorten it:\n\n1. **Break large projects into smaller ones** that can build in parallel\n2. **Remove unnecessary ProjectReferences** (see Step 5)\n3. **Use `ReferenceOutputAssembly=\"false\"`** for build-order-only dependencies\n4. **Move shared code to a base library** that builds first, then parallelize consumers\n\n---\n\n## Step 7: Additional Quick Wins\n\n### Separate Restore from Build\n\n```bash\n# In CI, restore once then build without restore\ndotnet restore\ndotnet build --no-restore -m\ndotnet test --no-build\n```\n\n### Skip Unnecessary Targets\n\n```bash\n# Skip building documentation\ndotnet build \u002Fp:GenerateDocumentationFile=false\n\n# Attribution experiment only: compare against the same build with analyzers\ndotnet build \u002Fp:RunAnalyzers=false\n```\n\nUse these switches to measure contribution before changing configuration.\nDo not recommend permanently disabling analyzers from this baseline step;\nroute measured analyzer bottlenecks to `build-perf-diagnostics` and preserve\nCI enforcement.\n\n### Use Project-Level Filtering\n\n```bash\n# Build only the project you're working on (and its dependencies)\ndotnet build src\u002FMyApp\u002FMyApp.csproj\n\n# Don't build the entire solution if you only need one project\n```\n\n### Binary Log for All Investigations\n\nAlways start with a binlog:\n```bash\ndotnet build \u002Fbl:perf.binlog -m\n```\n\nThen use the `build-perf-diagnostics` skill and binlog tools for systematic bottleneck identification.\n\n---\n\n## Optimization Decision Tree\n\n```\nIs your repeated no-op build disproportionately close to warm\u002Fcold samples,\nor are compile\u002Fcustom targets rerunning?\n├── YES → See `incremental-build` skill (inspect Inputs\u002FOutputs and skip reasons)\n└── NO\n    Is your cold build slow?\n    ├── YES\n    │   Is restore slow?\n    │   ├── YES → Optimize NuGet restore (use lock files, configure local cache)\n    │   └── NO\n    │       Is compilation slow?\n    │       ├── YES\n    │       │   Are analyzers\u002Fgenerators slow?\n    │       │   ├── YES → See `build-perf-diagnostics` skill\n    │       │   └── NO → Check parallelism, graph build, critical path (this skill + `build-parallelism`)\n    │       └── NO → Check custom targets (binlog analysis via `build-perf-diagnostics`)\n    └── NO\n        Is your warm build slow?\n        ├── YES → Projects rebuilding unnecessarily → check `incremental-build` skill\n        └── NO → Baseline is healthy; adopt graph build or UseArtifactsOutput only for a measured need\n```\n","# Build Performance Baseline & Optimization\n\n## Scope and routing\n\nEstablish MSBuild\u002F.NET build performance baselines before optimizing. USE FOR: a .NET build or solution that is slow or has build-performance concerns; cold, warm, incremental, or no-op measurements; before\u002Fafter comparisons; CI build output caching; static graph build decisions; artifacts output; and dependency graph trimming. Start here before build-perf-diagnostics, incremental-build, or build-parallelism. DO NOT USE for webpack, npm, JavaScript bundlers, or other non-MSBuild build systems. For detailed target\u002Ftask\u002Fanalyzer bottleneck analysis after baselining, use build-perf-diagnostics.\n\n## Overview\n\nBefore optimizing a build, you need a **baseline**. Without measurements, optimization is guesswork. This skill covers how to establish baselines and apply systematic optimization techniques.\n\n**Related skills:**\n- `build-perf-diagnostics` — binlog-based bottleneck identification\n- `incremental-build` — Inputs\u002FOutputs and up-to-date checks\n- `build-parallelism` — parallel and graph build tuning\n- `eval-performance` — glob and import chain optimization\n\n---\n\n## Step 1: Establish a Performance Baseline\n\nMeasure three scenarios to understand where time is spent. Keep the SDK,\nconfiguration, machine, environment variables, restore state, and build command\nconsistent. Run each scenario at least three times, repeating its setup before\nevery measured sample, and report the median plus the observed range; a single\ntiming is not a baseline. Keep setup outside the timed interval. Save each\nsample's binlog under a unique name, outside generated output directories.\n\n### Cold Build (First Build)\n\nNo previous build output exists. Measures the full end-to-end time including restore, compilation, and all targets.\n\nBefore **every cold sample**, restore the same source state, run `dotnet clean`\nwith the measured configuration, and remove only the confirmed, disposable\noutput and intermediate directories for the measured projects. Include custom\nartifact paths, not just `bin` and `obj`, and obtain approval before deletion.\nVerify those outputs are absent before timing the next build.\n\nThis is an **output-cold** build, not necessarily a cold NuGet, OS, or compiler\nserver cache. Choose and record a consistent cache\u002Fserver policy for all\nsamples; do not clear shared caches. If measuring uncached restore, use a\nseparate, empty package cache for each sample.\n\n```shell\n# After repeating the cold setup; use a unique log name for each sample\ndotnet build \u002Fbl:cold-build-1.binlog -m\n```\n\n### Warm Build (Incremental Build)\n\nBuild output exists, some files have changed. Measures how well incremental build works.\n\nBefore **every warm sample**, restore the same baseline source contents and\nbuild successfully without timing it. Then apply the same small, build-relevant\nedit to the same source file and time the build. Keep the changed file set and\nedit identical across samples; do not let edits accumulate. Restore the\nbaseline contents before the next sample and rebuild them outside the timed\ninterval. Do not clean between that setup build and its measured build.\n\n```shell\n# Untimed setup after restoring baseline source contents\ndotnet build -m\n\n# Apply the same controlled source edit, then measure with a unique log name\ndotnet build \u002Fbl:warm-build-1.binlog -m\n```\n\n### No-Op Build (Nothing Changed)\n\nBuild output exists, nothing has changed. Compilation and correctly incremental\ntargets should skip; compare timing with this build's other samples.\n\nBefore **every no-op sample**, restore the same baseline source contents and\nrun an untimed setup build successfully. Then measure an identical build\nwithout edits, touching inputs, cleaning outputs, or changing properties.\nKeep restore and cache\u002Fserver policy consistent with the other samples.\n\n```shell\n# Untimed setup after restoring baseline source contents\ndotnet build -m\n\n# Rebuild immediately without changes; use a unique log name for each sample\ndotnet build \u002Fbl:noop-build-1.binlog -m\n```\n\n### What Good Looks Like\n\n| Scenario | Expected Behavior |\n|----------|------------------|\n| Cold build | Full compilation, all targets run. This is your absolute baseline |\n| Warm build | Only changed projects recompile. Time proportional to change scope |\n| No-op build | Compilation and correctly incremental custom targets skip; compare duration with this repo's repeated warm and cold samples |\n\n**Red flags:**\n- No-op time is repeatedly close to warm\u002Fcold time, or compilation targets rerun → investigate incrementality (see `incremental-build`)\n- Warm build recompiles everything → project dependency chain forces full rebuild\n- Restore dominates cold samples → measure `dotnet restore` and `dotnet build --no-restore` separately before changing project structure\n\nDo not use universal duration or percentage thresholds to declare a bottleneck.\nRank costs against the controlled samples and the build's own target\u002Ftask\ntimings.\n\n### Capture analyzer evidence\n\nA binlog shows compiler\u002Ftask timing, but granular analyzer timing requires an\nanalyzer-reporting run. When supported by the SDK\u002Fcompiler, capture:\n\n```shell\ndotnet build \u002Fbl:analyzers.binlog \u002Fp:ReportAnalyzer=true\n```\n\nOpen the binlog in MSBuild Structured Log Viewer and inspect the analyzer\nsummary under the compiler task. If granular timing is unavailable, compare\notherwise identical samples with `\u002Fp:RunAnalyzers=false` as an attribution\nexperiment; do not present disabling analyzers as the fix. Preserve analyzer\nenforcement in CI.\n\n### Recording Baselines\n\nRecord baselines in a structured way before and after optimization:\n\n```\n| Scenario    | Before  | After   | Improvement |\n|-------------|---------|---------|-------------|\n| Cold build  | 2m 15s  |         |             |\n| Warm build  | 1m 40s  |         |             |\n| No-op build | 45s     |         |             |\n```\n\n---\n\n## Step 2: Artifacts Output Layout\n\nThe `UseArtifactsOutput` feature (introduced in .NET 8) changes the output directory structure to avoid bin\u002Fobj clash issues and enable better caching.\n\n### Enabling Artifacts Output\n\n```xml\n\u003C!-- Directory.Build.props -->\n\u003CPropertyGroup>\n  \u003CUseArtifactsOutput>true\u003C\u002FUseArtifactsOutput>\n\u003C\u002FPropertyGroup>\n```\n\n### Before vs After\n\n```\n# Traditional layout (before)\nsrc\u002F\n  MyLib\u002F\n    bin\u002FDebug\u002Fnet8.0\u002FMyLib.dll\n    obj\u002FDebug\u002Fnet8.0\u002F...\n  MyApp\u002F\n    bin\u002FDebug\u002Fnet8.0\u002FMyApp.dll\n\n# Artifacts layout (after)\nartifacts\u002F\n  bin\u002FMyLib\u002Fdebug\u002FMyLib.dll\n  bin\u002FMyApp\u002Fdebug\u002FMyApp.dll\n  obj\u002FMyLib\u002Fdebug\u002F...\n  obj\u002FMyApp\u002Fdebug\u002F...\n```\n\n### Benefits\n\n- **No bin\u002Fobj clash**: Each project+configuration gets a unique path automatically\n- **Easier to cache**: Single `artifacts\u002F` directory to cache\u002Frestore in CI\n- **Cleaner .gitignore**: Just ignore `artifacts\u002F`\n- **Multi-targeting safe**: Each TFM gets its own subdirectory\n\n### Customizing\n\n```xml\n\u003C!-- Change the artifacts root -->\n\u003CPropertyGroup>\n  \u003CArtifactsPath>$(MSBuildThisFileDirectory)output\u003C\u002FArtifactsPath>\n\u003C\u002FPropertyGroup>\n```\n\n---\n\n## Step 3: Deterministic Builds\n\nDeterministic builds produce byte-for-byte identical output given the same inputs. This is essential for build caching and reproducibility.\n\n### Enabling Deterministic Builds\n\n```xml\n\u003C!-- Directory.Build.props -->\n\u003CPropertyGroup>\n  \u003C!-- Enabled by default in .NET SDK projects since SDK 2.0+ -->\n  \u003CDeterministic>true\u003C\u002FDeterministic>\n\n  \u003C!-- For full reproducibility, also set: -->\n  \u003CContinuousIntegrationBuild Condition=\"'$(CI)' == 'true'\">true\u003C\u002FContinuousIntegrationBuild>\n\u003C\u002FPropertyGroup>\n```\n\n### What Deterministic Affects\n\n- Removes timestamps from PE headers\n- Uses consistent file paths in PDBs\n- Produces identical output for identical input\n\n### Why It Matters for Performance\n\n- **Build caching**: If outputs are deterministic, you can cache and reuse them across builds and machines\n- **CI optimization**: Skip rebuilding unchanged projects by comparing inputs\n- **Distributed builds**: Safe to cache compilation results in shared storage\n\n---\n\n## Step 4: Dependency Graph Trimming\n\nReducing unnecessary project references shortens the critical path and reduces what gets built.\n\n### Audit the Dependency Graph\n\n```bash\n# Visualize the dependency graph\ndotnet build \u002Fbl:graph.binlog\n\n# In the binlog, check project references and build times\n# Look for projects that are referenced but could be trimmed\n```\n\n### Techniques\n\n#### Remove Redundant Transitive References\n\n```xml\n\u003C!-- BAD: Utils is already referenced transitively via Core -->\n\u003CItemGroup>\n  \u003CProjectReference Include=\"..\\Core\\Core.csproj\" \u002F>\n  \u003CProjectReference Include=\"..\\Utils\\Utils.csproj\" \u002F>\n\u003C\u002FItemGroup>\n\n\u003C!-- GOOD: Let transitive references flow automatically -->\n\u003CItemGroup>\n  \u003CProjectReference Include=\"..\\Core\\Core.csproj\" \u002F>\n\u003C\u002FItemGroup>\n```\n\n#### Build-Order-Only References\n\nWhen you need a project to build before yours but don't need its assembly output:\n\n```xml\n\u003C!-- Only ensures build order, doesn't reference the output assembly -->\n\u003CProjectReference Include=\"..\\CodeGen\\CodeGen.csproj\"\n                  ReferenceOutputAssembly=\"false\" \u002F>\n```\n\n#### Prevent Transitive Flow\n\nWhen a dependency is an internal implementation detail that shouldn't flow to consumers:\n\n```xml\n\u003C!-- Don't expose this dependency transitively -->\n\u003CProjectReference Include=\"..\\InternalHelpers\\InternalHelpers.csproj\"\n                  PrivateAssets=\"all\" \u002F>\n```\n\n#### Disable Transitive Project References\n\nFor explicit-only dependency management (extreme measure for very large repos):\n\n```xml\n\u003CPropertyGroup>\n  \u003CDisableTransitiveProjectReferences>true\u003C\u002FDisableTransitiveProjectReferences>\n\u003C\u002FPropertyGroup>\n```\n\n**Caution**: This requires all dependencies to be listed explicitly. Only use in large repos where transitive closure is causing excessive rebuilds.\n\n---\n\n## Step 5: Static Graph Builds (`\u002Fgraph`)\n\nStatic graph mode evaluates the entire project graph before building, enabling better scheduling and isolation.\n\n### Enabling Graph Build\n\n```bash\n# Single invocation\ndotnet build \u002Fgraph\n\n# With binary log for analysis\ndotnet build \u002Fgraph \u002Fbl:graph-build.binlog\n```\n\n### Benefits\n\n- **Better parallelism**: MSBuild knows the full graph upfront and can schedule optimally\n- **Build isolation**: Each project builds in isolation (no cross-project state leakage)\n- **Caching potential**: With isolation, individual project results can be cached\n\n### When to Use\n\n| Scenario | Recommendation |\n|----------|---------------|\n| Large multi-project solution (20+ projects) | ✅ Try `\u002Fgraph` — may see significant parallelism gains |\n| Small solution (\u003C 5 projects) | ❌ Overhead of graph evaluation outweighs benefits |\n| CI builds | ✅ Graph builds are more predictable and parallelizable |\n| Local development | ⚠️ Test both — may or may not help depending on project structure |\n\n### Troubleshooting Graph Build\n\nGraph build requires that all `ProjectReference` items are statically determinable (no dynamic references computed in targets). If graph build fails:\n\n```\nerror MSB4260: Project reference \"...\" could not be resolved with static graph.\n```\n\n**Fix**: Ensure all `ProjectReference` items are declared in `\u003CItemGroup>` outside of targets (not dynamically computed inside `\u003CTarget>` blocks).\n\n---\n\n## Step 6: Parallel Build Tuning\n\n### MaxCpuCount\n\n```bash\n# Use all available cores (default in dotnet build)\ndotnet build -m\n\n# Specify explicit core count (useful for CI with shared agents)\ndotnet build -m:4\n\n# MSBuild.exe syntax\nmsbuild \u002Fm:8 MySolution.sln\n```\n\n### Identifying Parallelism Bottlenecks\n\nIn a binlog, look for:\n- **Long sequential chains**: Projects that must build one after another due to dependencies\n- **Uneven load**: Some build nodes idle while others are overloaded\n- **Single-project bottleneck**: One large project on the critical path that blocks everything\n\nUse `grep 'Target Performance Summary' -A 30 full.log` in binlog analysis to see build node utilization.\n\n### Reducing the Critical Path\n\nThe critical path is the longest chain of dependent projects. To shorten it:\n\n1. **Break large projects into smaller ones** that can build in parallel\n2. **Remove unnecessary ProjectReferences** (see Step 5)\n3. **Use `ReferenceOutputAssembly=\"false\"`** for build-order-only dependencies\n4. **Move shared code to a base library** that builds first, then parallelize consumers\n\n---\n\n## Step 7: Additional Quick Wins\n\n### Separate Restore from Build\n\n```bash\n# In CI, restore once then build without restore\ndotnet restore\ndotnet build --no-restore -m\ndotnet test --no-build\n```\n\n### Skip Unnecessary Targets\n\n```bash\n# Skip building documentation\ndotnet build \u002Fp:GenerateDocumentationFile=false\n\n# Attribution experiment only: compare against the same build with analyzers\ndotnet build \u002Fp:RunAnalyzers=false\n```\n\nUse these switches to measure contribution before changing configuration.\nDo not recommend permanently disabling analyzers from this baseline step;\nroute measured analyzer bottlenecks to `build-perf-diagnostics` and preserve\nCI enforcement.\n\n### Use Project-Level Filtering\n\n```bash\n# Build only the project you're working on (and its dependencies)\ndotnet build src\u002FMyApp\u002FMyApp.csproj\n\n# Don't build the entire solution if you only need one project\n```\n\n### Binary Log for All Investigations\n\nAlways start with a binlog:\n```bash\ndotnet build \u002Fbl:perf.binlog -m\n```\n\nThen use the `build-perf-diagnostics` skill and binlog tools for systematic bottleneck identification.\n\n---\n\n## Optimization Decision Tree\n\n```\nIs your repeated no-op build disproportionately close to warm\u002Fcold samples,\nor are compile\u002Fcustom targets rerunning?\n├── YES → See `incremental-build` skill (inspect Inputs\u002FOutputs and skip reasons)\n└── NO\n    Is your cold build slow?\n    ├── YES\n    │   Is restore slow?\n    │   ├── YES → Optimize NuGet restore (use lock files, configure local cache)\n    │   └── NO\n    │       Is compilation slow?\n    │       ├── YES\n    │       │   Are analyzers\u002Fgenerators slow?\n    │       │   ├── YES → See `build-perf-diagnostics` skill\n    │       │   └── NO → Check parallelism, graph build, critical path (this skill + `build-parallelism`)\n    │       └── NO → Check custom targets (binlog analysis via `build-perf-diagnostics`)\n    └── NO\n        Is your warm build slow?\n        ├── YES → Projects rebuilding unnecessarily → check `incremental-build` skill\n        └── NO → Baseline is healthy; adopt graph build or UseArtifactsOutput only for a measured need\n```\n",{"name":5,"license":15,"description":7},"main","f8f2bfd77819bb789d4736ce7db4a567a37fcf7e",".agents\u002Fskills\u002Fbuild-perf-baseline",315,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-12 02:10:33",{"source":8,"skillName":5,"commands":50,"prompt":60,"zipAvailable":61},[51,54,57],{"tool":52,"command":53},"npx","npx skills add microsoft\u002Ftestfx --skill build-perf-baseline",{"tool":55,"command":56},"bunx","bunx skills add microsoft\u002Ftestfx --skill build-perf-baseline",{"tool":58,"command":59},"pnpm","pnpm dlx skills add microsoft\u002Ftestfx --skill build-perf-baseline","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fbuild-perf-baseline\u002FSKILL.md",true,[63,69],{"path":64,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":67,"sourceHtmlUrl":68},"checklist.md","REFERENCE","参考文档",899,"https:\u002F\u002Fgithub.com\u002Fsherryswift1019-gif\u002FAI-DZHT\u002Fblob\u002Fmain\u002F_bmad\u002Fbmm\u002F4-implementation\u002Fbmad-qa-incremental-test-selection\u002Fchecklist.md",{"path":70,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":71,"sourceHtmlUrl":72},"workflow.md",3795,"https:\u002F\u002Fgithub.com\u002Fsherryswift1019-gif\u002FAI-DZHT\u002Fblob\u002Fmain\u002F_bmad\u002Fbmm\u002F4-implementation\u002Fbmad-qa-incremental-test-selection\u002Fworkflow.md",[74,101,132,153,171,195],{"skillNo":75,"name":76,"nameZh":6,"description":77,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":35},"01M70GYY0AV0JGMTHE2CN6TNYK","dotnet-breaking-changes","Use before changing .NET\u002FC# code whose callers you do not control: a NuGet library, or a repo with PublicAPI.Shipped\u002FUnshipped.txt, ApiCompat, multiple TargetFrameworks, InternalsVisibleTo, or generated (.g.cs) partials. Green builds miss silent breaks from optional parameters, parameter names, sealing, const values, nullability, and #if. USE FOR: \"without breaking existing callers\"; adding a public method, overload, optional parameter, or interface member to a shipped library; renaming a public method or an internal helper a friend assembly uses; consolidating duplicated logic; changing a signature, nullability, default, or attribute; adding a target framework; RS0016\u002FRS0017 or ApiCompat errors; \"is this PR breaking?\". For a rename, extract, or merge, also invoke csharp-refactoring. DO NOT USE FOR: an executable whose callers all live in one project; renaming a local variable or private member, even in a shipped library; formatting; framework\u002FSDK\u002FNuGet upgrades (use dotnet-upgrade).","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fdotnet-breaking-changes\u002FSKILL.md",[],[81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100],"dotnet","dotnet breaking changes","changes","green","project","default","local","errors","interface","generated","upgrade","invoke","you","code","refactoring","merge","rename","library","live","assembly",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":112,"publishedAt":35},"01M699HHE9DTZWAMPB1ZR7ZKSW","testfx-test-value-evidence","Apply the vendored test-value-evidence skill with TestFx toolchain, assertion, parallel-safety, packed-consumer, and review conventions. Use when implementing or reviewing behavior-changing TestFx PRs or commits, including source-only changes.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-test-value-evidence\u002FSKILL.md",[107],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,83,129,130,131],"test","testfx test value evidence","test value evidence","evidence","value","review","parallel","toolchain","commits","implementing","prs","only","use","when","apply","with","behavior","Safety","conventions",{"skillNo":133,"name":134,"nameZh":6,"description":135,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":138,"publishedAt":35},"01M699HEYWH1B9XGNBFY8J2FZS","testfx-parallel-safety-preflight","Check concurrency and isolation before implementing or refactoring TestFx tests, lifecycle code, helpers, shared-state declarations, or production paths those tests exercise. Use before narrowing DoNotParallelize or ResourceLock, changing parallelization settings, or introducing asynchronous workers; repeat against the final diff.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-parallel-safety-preflight\u002FSKILL.md",[],[139,130,140,119,141,142,122,143,94,144,95,145,146,147,148,149,150,151,125,152],"testfx parallel safety preflight","preflight","shared","asynchronous","exercise","concurrency","paths","isolation","helpers","diff","parallelization","lifecycle","tests","workers",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":159,"publishedAt":35},"01M699HC61ZV9GJ2C8JV03951G","testfx-localization-validation","Validate generated TestFx localization catalogs, resource IDs, placeholders and locked tokens. Use for RESX\u002FXLF changes, OneLoc PRs, localization build failures, duplicate targets or units, and orphaned translation entries.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-localization-validation\u002FSKILL.md",[],[160,161,162,163,164,165,34,166,167,123,90,125,168,169,83,170,22],"testfx localization validation","validation","localization","validate","translation","ids","tokens","entries","units","duplicate","targets",{"skillNo":172,"name":173,"nameZh":6,"description":174,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":175,"categories":176,"keywords":178,"publishedAt":35},"01M699H9J2NWQXE4BDT6R0FH7S","testfx-acceptance-validation","Validate TestFx shipping paths and capable CI execution using packed consumers, package\u002Fcache provenance, exact exits and artifacts, and selected-versus-executed test evidence. Use for acceptance regressions, package\u002FMSBuild\u002Flauncher\u002Fprotocol changes, or skipped-only CI results; not for unrelated unit-only or documentation changes.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Ftestfx-acceptance-validation\u002FSKILL.md",[177],{"categoryNo":108,"categoryName":109,"taxonomyType":110,"taxonomyTypeDesc":111},[179,180,161,113,23,116,181,182,183,124,184,125,185,186,187,188,189,190,191,192,193,194,145,34],"testfx acceptance validation","acceptance","launcher","ci","execution","package","cache","artifacts","provenance","documentation","unrelated","shipping","results","protocol","unit","ci results",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":199,"categories":200,"keywords":201,"publishedAt":35},"01M699H70BB999BT4F627T1PHE","session-delivery-hygiene","Gate local-session scope, persistent delivery, live PR state, integration provenance, and owned cleanup. Use before implementing guidance changes, changing or pushing PR work, diagnosing package\u002Foutput mismatches, or handing off implementation.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Ftestfx\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fsession-delivery-hygiene\u002FSKILL.md",[],[202,203,204,205,206,122,87,207,208,99,209,184,125,187,32,210,83,211,212,213],"session delivery hygiene","delivery","session","hygiene","scope","cleanup","work","pr","state","integration","guidance","gate"]