Skip to content

Pre-size MultiDictionary in CreateEvaluatedIncludeSnapshotIfRequested to avoid resize allocations#13377

Merged
JanProvaznik merged 1 commit intodotnet:mainfrom
nareshjo:dev/nareshjo/CreateEvaluatedIncludeSnapshotIfRequested--ReduceAlloc
Mar 19, 2026
Merged

Pre-size MultiDictionary in CreateEvaluatedIncludeSnapshotIfRequested to avoid resize allocations#13377
JanProvaznik merged 1 commit intodotnet:mainfrom
nareshjo:dev/nareshjo/CreateEvaluatedIncludeSnapshotIfRequested--ReduceAlloc

Conversation

@nareshjo
Copy link
Copy Markdown
Contributor

@nareshjo nareshjo commented Mar 12, 2026

This pull request was generated by the VS Perf Rel AI Agent. Please review this AI-generated PR with extra care! For more information, visit our wiki.

  • Issue: The allocation hot path ends in Microsoft.Build.Collections.MultiDictionary<string, ProjectItemInstance>.Add. In CreateEvaluatedIncludeSnapshotIfRequested, the MultiDictionary is constructed with default capacity (new MultiDictionary<string, ProjectItemInstance>(StringComparer.OrdinalIgnoreCase)) and then populated in a foreach loop over all project items.
    Each time the backing Dictionary<string, SmallList> exceeds its current capacity, Dictionary.Resize allocates a new Entry[] array — producing ~log₂(N) intermediate arrays that become immediate garbage. For a project with 4000 items this is ~12 resize allocations, with progressively larger arrays that can reach LOH thresholds.

  • Issue type: Specify an up-front capacity for collections if one is known.

  • Proposed fix: Add a capacity-aware constructor overload to MultiDictionary<K, V> and pass items.Count at the call site in CreateEvaluatedIncludeSnapshotIfRequested. The items parameter is ICollection so .Count is O(1). The capacity is an upper bound (unique key count ≤ item count), resulting in at most ~1–5% over-allocation — negligible compared to eliminating all resize allocations. MultiDictionary is an internal class; the existing constructor is preserved and the new overload is additive.
    This directly targets the Dictionary.Resize → Dictionary.Insert → MultiDictionary.Add → CreateEvaluatedIncludeSnapshotIfRequested frames seen in the stack trace that lead to TypeAllocated!Entry[System.String, MultiDictionary+SmallList[...]][].

Best practices wiki
See related failure in PRISM
ADO work item

Copilot AI review requested due to automatic review settings March 12, 2026 22:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a capacity-aware constructor to the internal MultiDictionary<K, V> class and uses it in CreateEvaluatedIncludeSnapshotIfRequested to pre-size the backing dictionary, avoiding repeated resize allocations when populating from a known-size collection.

Changes:

  • Added a new MultiDictionary(int capacity, IEqualityComparer<K> keyComparer) constructor that forwards the capacity to the backing Dictionary.
  • Updated the CreateEvaluatedIncludeSnapshotIfRequested call site to pass items.Count as the initial capacity.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/Build/Collections/MultiDictionary.cs Added capacity-aware constructor overload
src/Build/Instance/ProjectInstance.cs Pass items.Count as capacity when constructing the MultiDictionary

@JanProvaznik JanProvaznik merged commit b717f6f into dotnet:main Mar 19, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants