CtrlK
BlogDocsLog inGet started
Tessl Logo

optimizing-msbuild-performance

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.

74

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

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 and 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.

    // 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.

ScenarioUse
Property/item/target namesMSBuildNameIgnoreCaseComparer
General MSBuild identifiersStringComparison.OrdinalIgnoreCase
Dictionary keys for MSBuild namesMSBuildNameIgnoreCaseComparer as comparer
File paths on WindowsStringComparison.OrdinalIgnoreCase
File paths on LinuxStringComparison.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 PatternRecommended Type
Small fixed set (< ~8 items)Array or ReadOnlySpan<T>
Build-once, read-manyImmutableArray<T> (not ImmutableList<T>)
Keyed lookup, many itemsDictionary<TKey, TValue> with appropriate comparer
Keyed lookup, few items (< 5)Linear scan over array (cache-friendly, avoids dict overhead)
Concurrent reads, rare writesImmutableDictionary or snapshot pattern
Ordered iteration neededList<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 pathsFileMatcher.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-PatternWhy It's BadFix
items.Count() > 0Enumerates entire collectionitems.Any() or check .Count property
string.Format in log messages at Low importanceAllocates even if message is filteredUse structured logging or guard with verbosity check
new List<T>(enumerable).ToArray()Double allocationenumerable.ToArray() directly
dict.ContainsKey(k) then dict[k]Double lookupdict.TryGetValue(k, out var v)
Regex in a loop without RegexOptions.CompiledReinterprets pattern each timeCompile or use static Regex field
Repository
dotnet/msbuild
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.