-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Avoid allocations due to ConcurrentStack usage #12151
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 replaces the thread‐safe ConcurrentStack<GenericExpressionNode> pools with a plain Stack<GenericExpressionNode> guarded by a lock to eliminate per‐push node allocations and reduce GC pressure.
- Swaps all
ConcurrentStack<GenericExpressionNode>usage toStack<GenericExpressionNode>in the cache structure. - Adds a
lock(expressionPool)around popping/parsing logic inEvaluateConditionCollectingConditionedProperties. - Updates the
ExpressionTreeForCurrentOptionsWithSizeconstructor andGetOrAddsignatures to useStack<GenericExpressionNode>.
Comments suppressed due to low confidence (3)
src/Build/Evaluation/ConditionEvaluator.cs:251
- Add unit tests that simulate concurrent access to the new
Stack<GenericExpressionNode>pools, ensuring that both retrieval and return of parsed expressions under lock correctly preserve pool integrity and reuse.
lock (expressionPool)
src/Build/Evaluation/ConditionEvaluator.cs:251
- Only the pop-and-parse logic is wrapped by this lock, but the subsequent push back into the pool (still using
Stack.Push) occurs outside it. SinceStack<T>is not thread-safe, wrap both pop and push operations in the same lock to avoid race conditions.
lock (expressionPool)
src/Build/Evaluation/ConditionEvaluator.cs:255
- Declaring
parsedExpressioninside the lock block can limit its scope if you later need to use it outside. Consider moving the declaration immediately before thelockso its value is accessible throughout the remainder of the method.
GenericExpressionNode parsedExpression;
AR-May
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.
Looks good to me. I ran exp insertion, let's see if it passes.
JanProvaznik
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.
looks correct in the reasoning and code, let's see insertion
|
exp insertion is good: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/663329 |
The implementation of `ConcurrentStack<T>` creates a new wrapper node
each time an item is pushed onto the stack
public void Push(T item)
{
Node node = new Node(item);
node.m_next = m_head;
if (Interlocked.CompareExchange(ref m_head, node, node.m_next) !=
node.m_next)
{
PushCore(node, node);
}
}
The creates an appreciable amount of allocations during build.
These allocations can almost entirely be eliminated by using a vanilla
generic `Stack<T>` with a lock.
Fixes #
Context
The implementation of
ConcurrentStack<T>creates a new wrapper node each time an item is pushed onto the stackThe creates an appreciable amount of allocations during build:

These allocations can almost entirely be eliminated by using a vanilla generic
Stack<T>with a lock.Changes Made
Testing
Notes