-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Remove enumerator allocation in for (HashSet, HashSet) comparisons in SetEquals and IsProperSupersetOf. #78613
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
Remove enumerator allocation in for (HashSet, HashSet) comparisons in SetEquals and IsProperSupersetOf. #78613
Conversation
SetEquals and IsProperSupersetOf.
Tagging subscribers to this area: @dotnet/area-system-collections Issue DetailsWhen using HashSet.SetEquals, I noticed that the fast path didn't seem to be avoiding enumerator allocation.
|
namespace System.Collections.Generic | ||
{ | ||
internal ref struct BitHelper | ||
internal readonly ref struct BitHelper |
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.
Is there a reason not to have readonly here?
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.
It doesn't help or hurt anything, given its implementation and use. With a different implementation, where some or all of the bit state is stored in the instance, it wouldn't be appropriate. Logically given its surface area it doesn't make sense for it to be readonly, but it's also internal, so, doesn't really matter either way.
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.
How would you prefer I leave it?
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'd prefer not readonly, as it's confusing seeing a readonly struct with mutating methods (e.g. MarkBit).
|
||
// Already confirmed that the sets have the same number of distinct elements, so if | ||
// one is a superset of the other then they must be equal. | ||
return ContainsAllElements(otherAsSet); |
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.
After these changes, there is now only one caller for ContainsAllElements(IEnumerable<T>)
(IsSupersetOf
). Should the helper method just be inlined there?
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.
Sure
@madelson could you please provide benchmark numbers for the gain we are getting from this change? |
src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs
Show resolved
Hide resolved
@tarekgh I didn't see any benchmarks in dotnet/performance for this, so I just make a quick one which hits the path of 2 hash sets (100 ints) with the same comparer:
We see similar gains across both methods and that all allocation has been eliminated. |
src/libraries/Common/src/System/Collections/Generic/BitHelper.cs
Outdated
Show resolved
Hide resolved
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!
When using HashSet.SetEquals, I noticed that the fast path didn't seem to be avoiding enumerator allocation.