Skip to main content

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.

소스 정보

저장소
dotnet/msbuild
최근 소스 활동
2026년 4월 14일 15:35
감지된 SKILL.md 언어
영어
스타
5,552
포크
1,457

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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.
# 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 |
GitHub에서 보기