Skip to content

Resolve dependency graph items reduce allocation size#7174

Merged
Nigusu-Allehu merged 4 commits intoNuGet:devfrom
nareshjo:ResolveDependencyGraphItems-ReduceAllocationSize
Feb 27, 2026
Merged

Resolve dependency graph items reduce allocation size#7174
Nigusu-Allehu merged 4 commits intoNuGet:devfrom
nareshjo:ResolveDependencyGraphItems-ReduceAllocationSize

Conversation

@nareshjo
Copy link
Copy Markdown
Contributor

🤖 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: ResolveDependencyGraphItemsAsync creates a ResolvedDependencyGraphItem for every resolved dependency in the graph. Each item's Suppressions list is initialized as new List { item }.

    This uses the default List constructor (capacity 0), followed by .Add(). The first .Add() triggers EnsureCapacity, which grows the backing array to the List default 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.

    Suppressions is init-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:

nuget.commands.dll!NuGet.Commands.DependencyGraphResolver+<ResolveDependencyGraphItemsAsync>d__.MoveNext
mscorlib.dll!System.Collections.Generic.List`[System.__Canon].Add
mscorlib.dll!System.Collections.Generic.List`[System.__Canon].EnsureCapacity
mscorlib.dll!System.Collections.Generic.List`[System.__Canon].set_Capacity
clr.dll!JIT_NewArr1
clr.dll!AllocateArrayEx
clr.dll!SVR::GCHeap::Alloc
clr.dll!SVR::gc_heap::try_allocate_more_space
clr.dll!GCToCLREventSink::FireGCAllocationTick_V3
clr.dll!CoTemplate_qqhxpzqp
clr.dll!EtwCallout
clr.dll!ETW::SamplingLog::SendStackTrace
TypeAllocated!System.Collections.Generic.HashSet`1[NuGet.Commands.DependencyGraphResolver+LibraryDependencyIndex][]
  • Issue type: Reduce over-sized backing-array allocations in the dependency graph resolution hot loop

  • Proposed fix: Add capacity: 1 to all 3 single-element list initializations in ResolveDependencyGraphItemsAsync.

    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 triggers EnsureCapacity. 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.

    Suppressions is init-only and never mutated after construction anywhere in the codebase — confirmed by checking all 12 references. This makes capacity: 1 a guaranteed exact fit with no risk of under-allocation triggering a later resize.

Best practices wiki
See related failure in PRISM
ADO work item

@nareshjo nareshjo requested a review from a team as a code owner February 26, 2026 21:27
@dotnet-policy-service dotnet-policy-service Bot added the Community PRs created by someone not in the NuGet team label Feb 26, 2026
Comment thread src/NuGet.Core/NuGet.Commands/RestoreCommand/DependencyGraphResolver.cs Outdated
Comment thread src/NuGet.Core/NuGet.Commands/RestoreCommand/DependencyGraphResolver.cs Outdated
@Nigusu-Allehu Nigusu-Allehu enabled auto-merge (squash) February 27, 2026 18:15
@Nigusu-Allehu Nigusu-Allehu merged commit 88c27a6 into NuGet:dev Feb 27, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Community PRs created by someone not in the NuGet team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants