Resolve dependency graph items reduce allocation size#7174
Merged
Nigusu-Allehu merged 4 commits intoNuGet:devfrom Feb 27, 2026
Merged
Conversation
added 2 commits
February 26, 2026 13:25
nkolev92
reviewed
Feb 26, 2026
nkolev92
approved these changes
Feb 27, 2026
Nigusu-Allehu
approved these changes
Feb 27, 2026
This was referenced Mar 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 AI-Generated Pull Request 🤖
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. Please share feedback with TIP Insights
Issue:
ResolveDependencyGraphItemsAsynccreates aResolvedDependencyGraphItemfor every resolved dependency in the graph. Each item'sSuppressionslist is initialized asnew List { item }.This uses the default
Listconstructor (capacity 0), followed by.Add(). The first.Add()triggersEnsureCapacity, which grows the backing array to theListdefault of 4 slots — but only 1 slot is ever used. 75% of each backing array is allocated and never touched.These over-sized arrays are long-lived — each is held on a ResolvedDependencyGraphItem that survives the full duration of graph resolution and target graph creation per target framework, long enough to potentially get promoted to Gen1.
Suppressionsisinit-only and never mutated after construction — every reference in the codebase is a read (.Count,[0],foreach). No.Add(),.Remove(), or.Clear()is ever called on an existing list.For a typical project with ~500 transitive dependencies across 2 target frameworks, this produces ~1,000 over-sized backing arrays per restore. For larger enterprise projects or multi-target libraries, the waste scales linearly.
This matches the allocation stack flow showing the over-sized array allocation path firing inside the resolve loop:
Issue type: Reduce over-sized backing-array allocations in the dependency graph resolution hot loop
Proposed fix: Add
capacity: 1to all 3 single-element list initializations inResolveDependencyGraphItemsAsync.The 3 sites (first-seen, eviction, same-version replacement) always store exactly 1 element. With
capacity: 1, the constructor allocates a single-element array directly, so.Add()no longer triggersEnsureCapacity. Each backing array goes from 4 slots (3 wasted) to 1 slot (0 wasted). The total number of allocations remains the same (one array per resolved item), but each allocation is 75% smaller.Suppressionsisinit-only and never mutated after construction anywhere in the codebase — confirmed by checking all 12 references. This makescapacity: 1a guaranteed exact fit with no risk of under-allocation triggering a later resize.Best practices wiki
See related failure in PRISM
ADO work item