-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Improve throughput of Enumerable.Contains for default comparer #25097
Conversation
When no comparer or a null comparer is specified to Enumerable.Contains with a non-collection enumerable, `EqualityComparer<TSource>.Default` is used. However, with recent improvements to the JIT around devirtualization and inlining of `EqualityComparer<TSource>.Default`, we can do much better for this case by having a loop dedicated to the comparer==null case. For an enumerable of ints, this speeds up Contains in my measurements by up to 33% (which logically makes sense, as we're effectively eliminating one of three interface calls invoked per element).
if (source == null) | ||
{ | ||
throw Error.ArgumentNull(nameof(source)); | ||
} | ||
|
||
foreach (TSource element in source) | ||
if (comparer == null) |
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.
Would it be worth catching explicitly passed EqualityComparer<TSource>.Default
too?
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 that common?
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.
That's pretty much what I'm wondering. I would guess it would be more common with direct collection operations than with linq to always pass in a comparer and have Default
as the default, but that's only a guess.
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'm not going to right now. Other than size increase, this change doesn't penalize any cases (e.g. it's using a branch that's already there). Adding such a check in theory could negatively impact other cases, even if it might be negligible.
LGTM too. |
…otnet#25097) This was removed accidentally as part of overhauling the async infrastructure in 2.1. VS depends on it for some task tracking logic. Signed-off-by: dotnet-bot <[email protected]>
…25097) This was removed accidentally as part of overhauling the async infrastructure in 2.1. VS depends on it for some task tracking logic. Signed-off-by: dotnet-bot <[email protected]>
…t/corefx#25097) When no comparer or a null comparer is specified to Enumerable.Contains with a non-collection enumerable, `EqualityComparer<TSource>.Default` is used. However, with recent improvements to the JIT around devirtualization and inlining of `EqualityComparer<TSource>.Default`, we can do much better for this case by having a loop dedicated to the comparer==null case. For an enumerable of ints, this speeds up Contains in my measurements by up to 33% (which logically makes sense, as we're effectively eliminating one of three interface calls invoked per element). Commit migrated from dotnet/corefx@4cc7cd6
When no comparer or a null comparer is specified to Enumerable.Contains with a non-collection enumerable,
EqualityComparer<TSource>.Default
is used. However, with recent improvements to the JIT around devirtualization and inlining ofEqualityComparer<TSource>.Default
, we can do much better for this case by having a loop dedicated to the comparer==null case. For an enumerable of ints, this speeds up Contains in my measurements by up to 33% (which logically makes sense, as we're effectively eliminating one of three interface calls invoked per element).cc: @VSadov, @OmarTawfik, @AndyAyersMS