Infra, Ops & WorkflowSûr100100/100

Optimizing Msbuild Performance (.NET)

Guides performance optimization for MSBuild engine code. Consult when working on hot paths in evaluation or execution, reducing allocations, choosing collection types, handling strings efficiently, modifying Expander.cs or Evaluator.cs, using Span<T>/stackalloc, caching values, or profiling build performance. Also applies when reviewing PRs for performance regression.

ou envoie-le directement à ton agent.

Installer dans ton projet

$ curl -fsSL https://arboristhree.xyz/cli/install-skill.mjs -o /tmp/arboris-install-skill.mjs && node /tmp/arboris-install-skill.mjs optimizing-msbuild-performance__dotnet-dotnet__src-msbuild-github-skills-7c9adcaf12db
.NET
dotnet/dotnet
Contenu à copier
---
name: optimizing-msbuild-performance
description: 'Guides performance optimization for MSBuild engine code. Consult when working on hot paths in evaluation or execution, reducing allocations, choosing collection types, handling strings efficiently, modifying Expander.cs or Evaluator.cs, using Span<T>/stackalloc, caching values, or profiling build performance. Also applies when reviewing PRs for performance regression.'
argument-hint: 'Describe the performance-sensitive code area or optimization goal.'
metadata:
  origin: .NET
---

# MSBuild Performance Guidelines

MSBuild evaluates and builds thousands of projects in enterprise solutions. Performance is an architectural concern, not an afterthought.

## Guiding Principle

**Profile before optimizing; measure, do not guess.** Use BenchmarkDotNet, the evaluation profiler (`/profileevaluation`), or ETW traces to identify actual bottlenecks before optimizing.

See [evaluation-profiling.md](../../../documentation/evaluation-profiling.md) and [General_perf_onepager.md](../../../documentation/specs/proposed/General_perf_onepager.md) for profiling tools.

## Allocation Awareness on Hot Paths

The evaluation and execution engines process millions of operations per build. Unnecessary allocations cause GC pressure that compounds across large solutions.

### Rules

1. **Avoid LINQ in hot paths.** `Where`, `Select`, `Any`, `First` all allocate enumerator objects and delegate closures. Use `foreach` loops instead.

   ```csharp
   // BAD — allocates iterator + delegate
   var match = items.FirstOrDefault(i => i.Name == name);

   // GOOD — zero allocations
   foreach (var item in items)
   {
       if (item.Name == name) { /* found */ break; }
   }
   ```

2. **Avoid string allocations in formatting.** Use `string.Concat`, interpolation with `Span<T>`, or `StringBuilder` reuse — not `string.Format` on hot paths.

3. **Prefer `Span<T>` and `stackalloc` for short-lived buffers** when parsing or slicing strings. Avoid `Substring` when you only need to compare or inspect a portion.

4. **Cache computed values.** If a value is computed in a loop but doesn't change, hoist it out. If it's expensive and reused across calls, use a field or `Lazy<T>`.

## String Comparison Rules

MSBuild property, item, and target names are **case-insensitive**. Getting this wrong causes subtle bugs and perf issues.

| Scenario | Use |
|----------|-----|
| Property/item/target names | `MSBuildNameIgnoreCaseComparer` |
| General MSBuild identifiers | `StringComparison.OrdinalIgnoreCase` |
| Dictionary keys for MSBuild names | `MSBuildNameIgnoreCaseComparer` as comparer |
| File paths on Windows | `StringComparison.OrdinalIgnoreCase` |
| File paths on Linux | `StringComparison.Ordinal` |

**Never** use `ToLower()`/`ToUpper()` for comparisons — they allocate a new string every time.
**Never** use `CurrentCulture` for MSBuild identifiers — build behavior must not vary by locale.

## Collection Type Selection

| Access Pattern | Recommended Type |
|---------------|-----------------|
| Small fixed set (< ~8 items) | Array or `ReadOnlySpan<T>` |
| Build-once, read-many | `ImmutableArray<T>` (not `ImmutableList<T>`) |
| Keyed lookup, many items | `Dictionary<TKey, TValue>` with appropriate comparer |
| Keyed lookup, few items (< 5) | Linear scan over array (cache-friendly, avoids dict overhead) |
| Concurrent reads, rare writes | `ImmutableDictionary` or snapshot pattern |
| Ordered iteration needed | `List<T>` or array; avoid `HashSet<T>` if order matters |

**`ImmutableList<T>` is almost never the right choice** — it has O(log n) access vs O(1) for `ImmutableArray<T>`.

## Hot Path Identification

These areas are performance-critical and require extra scrutiny:

- **`Expander.cs`** — property/item/metadata expansion during evaluation
- **`Evaluator.cs`** — project evaluation orchestration
- **`ItemSpec.cs` / `LazyItemEvaluator.cs`** — item evaluation and globbing
- **`TaskExecutionHost.cs`** — task parameter marshaling
- **File I/O paths** — `FileMatcher.cs`, glob operations, project loading

### Inlining Considerations

For extremely hot methods, consider:
- `[MethodImpl(MethodImplOptions.AggressiveInlining)]` for small methods called millions of times
- Avoiding virtual dispatch in inner loops
- Using `struct` enumerators to avoid heap allocation

## Evaluation Performance Specifics

- **Import chain depth** affects evaluation time linearly. Minimize unnecessary imports.
- **Glob patterns** are evaluated per-project. Overly broad globs (`**/*`) are expensive.
- **Condition evaluation** should use short-circuit logic — put cheap checks first.
- **Property functions** (`$([System.IO.Path]::...)`) are interpreted and slower than built-in operations.

## Anti-Patterns

| Anti-Pattern | Why It's Bad | Fix |
|-------------|-------------|-----|
| `items.Count() > 0` | Enumerates entire collection | `items.Any()` or check `.Count` property |
| `string.Format` in log messages at Low importance | Allocates even if message is filtered | Use structured logging or guard with verbosity check |
| `new List<T>(enumerable).ToArray()` | Double allocation | `enumerable.ToArray()` directly |
| `dict.ContainsKey(k) then dict[k]` | Double lookup | `dict.TryGetValue(k, out var v)` |
| Regex in a loop without `RegexOptions.Compiled` | Reinterprets pattern each time | Compile or use static `Regex` field |
Colle ce Markdown dans ton agent ou utilise les boutons ci-dessus pour l’écrire dans ton projet.

Ce que fait Optimizing Msbuild Performance (.NET)

**`Expander.cs`** — property/item/metadata expansion during evaluation
**`Evaluator.cs`** — project evaluation orchestration
**`ItemSpec.cs` / `LazyItemEvaluator.cs`** — item evaluation and globbing
**`TaskExecutionHost.cs`** — task parameter marshaling

Comment utiliser Optimizing Msbuild Performance (.NET)

1
Copie le prompt
Un clic copie le prompt packagé (ou l'envoie à ton agent).
2
L'agent installe le skill
Il ajoute le SKILL.md et ses ressources à ton projet.
3
Activation automatique
Le skill s'active dès que le contexte correspond.

Déclencheurs pour Optimizing Msbuild Performance (.NET)

Dis simplement à ton agent quelque chose comme :

Applique le skill optimizing-msbuild-performance__dotnet-dotnet__src-msbuild-github-skills-7c9adcaf12db à cette tâche
Utilise optimizing-msbuild-performance__dotnet-dotnet__src-msbuild-github-skills-7c9adcaf12db pour améliorer cette implémentation
Passe en revue ce sujet avec optimizing-msbuild-performance__dotnet-dotnet__src-msbuild-github-skills-7c9adcaf12db

Skills liés à Optimizing Msbuild Performance (.NET)

Add CLI Command (.NET)
Add or change a dotnet CLI command, subcommand, or option across the relevant CLI projects. USE FOR: adding or changing a dotnet CLI command/subcommand, adding a new Option<T> or Argument<T>, registering a subcommand in the command tree, changing an option's help description or a command's runtime message, wiring a command parser, or updating --help output.
Add Dotnet Aot Command (.NET)
Add, enable, or review a dotnet CLI command or feature in the Native AOT CLI (src/Cli/dotnet-aot) and prove its compatibility. USE FOR: migrating a command or option to AOT, reviewing an AOT migration PR, defining conservative eligibility and managed fallback, changing AotSourceFiles.props or AotDependencies.props, validating AOT/managed parity, NativeAOT-publishing tests, checking binary-size impact, or using the dn harness and separated SDK layout. DO NOT USE FOR: resolving IL trim/AOT analyzer warnings alone (use dotnet-aot-compat), running dotnet.Tests incrementally (use incremental-test), or pure managed CLI work.
Add Net Analyzer (.NET)
Add, port, or change a .NET code analysis rule (CA####) under src/Microsoft.CodeAnalysis.NetAnalyzers. USE FOR: implementing a new CA analyzer and its code fixer, porting a rule or PR from the retired dotnet/roslyn-analyzers repo, allocating a diagnostic ID from DiagnosticCategoryAndIdRanges.txt, choosing RuleLevel/severity/category, wiring resx + xlf strings, recording the rule in AnalyzerReleases.Unshipped.md, regenerating the analyzer documentation/sarif files, and writing MSTest analyzer/code-fix tests with the VerifyCS/VerifyVB harness. DO NOT USE FOR: NETSDK#### MSBuild diagnostics (src/Tasks), CS####/BC#### compiler diagnostics or IDE#### analyzers (dotnet/roslyn), or CONTAINER#### diagnostics (src/Containers).