-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Reduce heap size of RegisteredTaskRecord #12284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this 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 optimizes memory usage by replacing ConcurrentDictionary with a regular Dictionary protected by locks in the RegisteredTaskRecord class. The change addresses heap pressure during solution load where ConcurrentDictionary objects were consuming significant memory (30MB+ of surviving objects).
- Replaces
ConcurrentDictionary<RegisteredTaskIdentity, object>withDictionary<RegisteredTaskIdentity, object> - Implements manual locking around dictionary access to maintain thread safety
- Reduces memory overhead by 5x-15x based on profiling data
Comments suppressed due to low confidence (1)
src/Build/Instance/TaskRegistry.cs:1441
- The finally block is missing a closing brace. There should be two closing braces - one for the lock block and one for the finally block.
_taskNamesCreatableByFactory[taskIdentity] = creatableByFactory;
|
@surayya-MS heads-up, it's related to your recent changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! There are definitely reduced memory optimizations here.
However, I'm still leaning towards using ConcurrentDictionary instead of normal Dictionary + lock for the new Multi-threaded MSBuild feature because of optimized concurrent reads and writes.
@rainersigwald what do you think?
I'd be curious how much contention you're actually seeing in these paths. From the heap dump, there are 41,000 The other thing to weigh with respect to performance in the Multi-threaded feature is that garbage collections are going to be even more impactful to performance. Just these |
rainersigwald
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my big question here is "why are there enough of these RegisteredTaskRecords to matter?"
The set of tasks is pretty uniform between projects--should we tackle the size here by refactoring to share a single task registration object per resolved task, so e.g. in normal cases there's only one Csc? Is it per-project now?
rainersigwald
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change looks fine to me for now/if we don't pursue the better refactoring.
Fixes # ### Context Part of GC cost is the amount of surviving objects during a collection. During solution load, this stack showed up as currently rooted items: <img width="1198" height="259" alt="image" src="https://github.com/user-attachments/assets/b524dded-4941-43b9-95e7-a0bf7d6fcab4" /> You can see that there are roughly 30MB worth of `object` instances being held onto by the tables collection inside of `ConcurrentDictionary`. Additionally, `ConcurrentDictionary` objects are significantly larger than a `Dictionary`. For instance: 10k of them when empty: <img width="1200" height="89" alt="image" src="https://github.com/user-attachments/assets/38d2cda3-5a27-4a27-b902-d0a8d20e07f4" /> 10k of them with one item: <img width="1258" height="158" alt="image" src="https://github.com/user-attachments/assets/6dd439ad-6689-4a09-9d1d-9f4fd8adda75" /> This is a size difference between 5x and 15x. The existing implementation can be swapped to just use a dictionary with a lock. ### Changes Made ### Testing ### Notes
Fixes #
Context
Part of GC cost is the amount of surviving objects during a collection. During solution load, this stack showed up as currently rooted items:
You can see that there are roughly 30MB worth of
objectinstances being held onto by the tables collection inside ofConcurrentDictionary.Additionally,
ConcurrentDictionaryobjects are significantly larger than aDictionary. For instance:10k of them when empty:
10k of them with one item:
This is a size difference between 5x and 15x.
The existing implementation can be swapped to just use a dictionary with a lock.
Changes Made
Testing
Notes