Perf: Remove LinkedList usage in ItemDictionary#12345
Merged
YuliiaKovalova merged 4 commits intodotnet:mainfrom Oct 27, 2025
Merged
Perf: Remove LinkedList usage in ItemDictionary#12345YuliiaKovalova merged 4 commits intodotnet:mainfrom
YuliiaKovalova merged 4 commits intodotnet:mainfrom
Conversation
Contributor
There was a problem hiding this comment.
Pull Request Overview
This PR removes LinkedList usage from ItemDictionary and replaces it with a simpler List-based implementation for significant performance improvements. The change eliminates the O(1) LinkedListNode lookup table that was maintained for every dictionary entry, reducing memory allocations and garbage collection pressure.
Key Changes
- Replaced LinkedList with List in ItemDictionary for better performance
- Removed several unused ItemDictionary APIs (Contains, AddRange, Replace, etc.)
- Introduced batch-based remove operations that build HashSets on-demand
- Added ItemDictionarySlim for internal scope management without locking overhead
Reviewed Changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| ImmutableItemDictionary.cs | Removed unused API methods and simplified interface |
| ItemDictionary.cs | Core refactor from LinkedList to List with optimized remove operations |
| IItemDictionary.cs | Updated interface to remove unused methods and add batch remove |
| TargetUpToDateChecker.cs | Updated to use ImportItemsOfType instead of AddEmptyMarker |
| TargetEntry.cs | Added TruncateLookupsForItemTypes calls for proper scope handling |
| Lookup.cs | Major refactor with new ItemDictionarySlim and optimized item resolution |
| ItemBucket.cs | Updated to use TruncateLookupsForItemTypes instead of PopulateWithItems |
| ItemGroupIntrinsicTask.cs | Updated Remove call to use new batch API |
| BatchingEngine.cs | Updated to use FrozenSet for item names |
| Test files | Updated test code to match new APIs |
Comments suppressed due to low confidence (1)
src/Build/Collections/ItemDictionary.cs:1
- The linear search through the list for removing a single item creates O(n) performance for removes. Since this is a hot path and performance is critical, consider using a Dictionary<T, int> index lookup table for O(1) removes, or document that single removes are expected to be rare compared to batch removes.
// Licensed to the .NET Foundation under one or more agreements.
d60e258 to
cc0b9e4
Compare
ccastanedaucf
commented
Oct 1, 2025
YuliiaKovalova
approved these changes
Oct 27, 2025
This was referenced Oct 27, 2025
This was referenced Nov 19, 2025
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.
NOTE:
First couple commits are duplicated from Perf: Reimplement Lookup.Scope tables without ItemDictionary #12320 - it removes someForce pushed the rebased version.ItemDictionaryAPIs that simplify this refactor by eliminating most use cases for the O(1) lookup, so just ignore them for now.Context
After #12320 , the O(1)
LinkedListNodelookup inItemDictionaryis only used for removes.If removes are batched by the same item type (see new
RemoveItemsByItemType(), the cost to build aHashSetlookup on-demand is trivial compared to the current approach of maintaining an additional O(1) lookup table with every dictionary entry - especially given that removes are only used in a few scenarios.Perf
Working set memory at end of build (-17% and nearly the entire Large Object Heap):

Before
After

Total allocations (-300MB, finally no longer double digits!):

Before
After

This one also has a pretty significant drop in total GC time relative to allocations, I'm guessing due to the difference in LOH? I compared 4 profiles here to make sure this wasn't noise.
Before

After

This also further reduces CPU when merging down to the base Scope. Much of the remaining cost here was related to updating the additional dictionary, and this is also the only location where batch removes occur.
Before

After
