Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Improve throughput of Enumerable.Contains for default comparer #25097

Merged
merged 1 commit into from
Nov 8, 2017

Conversation

stephentoub
Copy link
Member

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).

cc: @VSadov, @OmarTawfik, @AndyAyersMS

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)
Copy link
Contributor

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that common?

Copy link
Contributor

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.

Copy link
Member Author

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.

@AndyAyersMS
Copy link
Member

LGTM too.

@danmoseley danmoseley merged commit 4cc7cd6 into dotnet:master Nov 8, 2017
@stephentoub stephentoub deleted the contains_default branch November 13, 2017 15:39
@karelz karelz added this to the 2.1.0 milestone Nov 18, 2017
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corefx that referenced this pull request Jun 12, 2019
…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]>
stephentoub added a commit that referenced this pull request Jun 12, 2019
…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]>
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…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
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants