From 2d6695be58a1df777105d37193d3ef23bbf6147f Mon Sep 17 00:00:00 2001 From: neon-sunset Date: Thu, 30 May 2024 18:28:45 +0300 Subject: [PATCH 1/2] Add fast path for count with predicate --- src/libraries/System.Linq/src/System/Linq/Count.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libraries/System.Linq/src/System/Linq/Count.cs b/src/libraries/System.Linq/src/System/Linq/Count.cs index 6b8819f8c3cbc..f2c5b8e26f24d 100644 --- a/src/libraries/System.Linq/src/System/Linq/Count.cs +++ b/src/libraries/System.Linq/src/System/Linq/Count.cs @@ -60,6 +60,19 @@ public static int Count(this IEnumerable source, Func span)) + { + foreach (TSource element in span) + { + if (predicate(element)) + { + count++; + } + } + + return count; + } + foreach (TSource element in source) { checked From 3f2a932078ddb8c2bacbf96b453237bf2a1cd5fe Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 22 Jul 2024 12:14:51 -0400 Subject: [PATCH 2/2] Also use TryGetSpan in Aggregate, Any, All, Contains, First, and Single --- .../System.Linq/src/System/Linq/Aggregate.cs | 52 ++++++++++++++++--- .../System.Linq/src/System/Linq/AnyAll.cs | 38 +++++++++++--- .../System.Linq/src/System/Linq/Contains.cs | 21 +++++++- .../System.Linq/src/System/Linq/Count.cs | 29 +++++------ .../System.Linq/src/System/Linq/First.cs | 22 ++++++-- .../System.Linq/src/System/Linq/Single.cs | 24 ++++++++- 6 files changed, 150 insertions(+), 36 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/Aggregate.cs b/src/libraries/System.Linq/src/System/Linq/Aggregate.cs index c1a4377c29a8a..0d6ea4cf26cff 100644 --- a/src/libraries/System.Linq/src/System/Linq/Aggregate.cs +++ b/src/libraries/System.Linq/src/System/Linq/Aggregate.cs @@ -19,21 +19,37 @@ public static TSource Aggregate(this IEnumerable source, Func< ThrowHelper.ThrowArgumentNullException(ExceptionArgument.func); } - using (IEnumerator e = source.GetEnumerator()) + TSource result; + if (source.TryGetSpan(out ReadOnlySpan span)) { + if (span.IsEmpty) + { + ThrowHelper.ThrowNoElementsException(); + } + + result = span[0]; + for (int i = 1; i < span.Length; i++) + { + result = func(result, span[i]); + } + } + else + { + using IEnumerator e = source.GetEnumerator(); + if (!e.MoveNext()) { ThrowHelper.ThrowNoElementsException(); } - TSource result = e.Current; + result = e.Current; while (e.MoveNext()) { result = func(result, e.Current); } - - return result; } + + return result; } public static TAccumulate Aggregate(this IEnumerable source, TAccumulate seed, Func func) @@ -49,9 +65,19 @@ public static TAccumulate Aggregate(this IEnumerable span)) { - result = func(result, element); + foreach (TSource element in span) + { + result = func(result, element); + } + } + else + { + foreach (TSource element in source) + { + result = func(result, element); + } } return result; @@ -75,9 +101,19 @@ public static TResult Aggregate(this IEnumerable< } TAccumulate result = seed; - foreach (TSource element in source) + if (source.TryGetSpan(out ReadOnlySpan span)) { - result = func(result, element); + foreach (TSource element in span) + { + result = func(result, element); + } + } + else + { + foreach (TSource element in source) + { + result = func(result, element); + } } return resultSelector(result); diff --git a/src/libraries/System.Linq/src/System/Linq/AnyAll.cs b/src/libraries/System.Linq/src/System/Linq/AnyAll.cs index 28a3783aa8829..b236c6a6f1ac6 100644 --- a/src/libraries/System.Linq/src/System/Linq/AnyAll.cs +++ b/src/libraries/System.Linq/src/System/Linq/AnyAll.cs @@ -55,11 +55,24 @@ public static bool Any(this IEnumerable source, Func span)) { - if (predicate(element)) + foreach (TSource element in span) { - return true; + if (predicate(element)) + { + return true; + } + } + } + else + { + foreach (TSource element in source) + { + if (predicate(element)) + { + return true; + } } } @@ -78,11 +91,24 @@ public static bool All(this IEnumerable source, Func span)) + { + foreach (TSource element in span) + { + if (!predicate(element)) + { + return false; + } + } + } + else { - if (!predicate(element)) + foreach (TSource element in source) { - return false; + if (!predicate(element)) + { + return false; + } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Contains.cs b/src/libraries/System.Linq/src/System/Linq/Contains.cs index f242c7a35f52a..f4dbd43ae00e3 100644 --- a/src/libraries/System.Linq/src/System/Linq/Contains.cs +++ b/src/libraries/System.Linq/src/System/Linq/Contains.cs @@ -20,9 +20,28 @@ public static bool Contains(this IEnumerable source, TSource v if (comparer is null) { + // While it's tempting, this must not delegate to ICollection.Contains, as the historical semantics + // of a null comparer with this method are to use EqualityComparer.Default, and that might differ + // from the semantics encoded in ICollection.Contains. + + // We don't bother special-casing spans here as explicitly providing a null comparer with a known collection type + // is relatively rare. If you don't care about the comparer, you use the other overload, and while it will delegate + // to this overload with a null comparer, it'll only do so for collections from which we can't extract a span. + // And if you do care about the comparer, you're generally passing in a non-null one. + foreach (TSource element in source) { - if (EqualityComparer.Default.Equals(element, value)) // benefits from devirtualization and likely inlining + if (EqualityComparer.Default.Equals(element, value)) + { + return true; + } + } + } + else if (source.TryGetSpan(out ReadOnlySpan span)) + { + foreach (TSource element in span) + { + if (comparer.Equals(element, value)) { return true; } diff --git a/src/libraries/System.Linq/src/System/Linq/Count.cs b/src/libraries/System.Linq/src/System/Linq/Count.cs index f2c5b8e26f24d..349a7faecd099 100644 --- a/src/libraries/System.Linq/src/System/Linq/Count.cs +++ b/src/libraries/System.Linq/src/System/Linq/Count.cs @@ -69,17 +69,14 @@ public static int Count(this IEnumerable source, Func(this IEnumerable source) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source); } + // TryGetSpan isn't used here because if it's expected that there are more than int.MaxValue elements, + // the source can't possibly be something from which we can extract a span. + long count = 0; using (IEnumerator e = source.GetEnumerator()) { - checked + while (e.MoveNext()) { - while (e.MoveNext()) - { - count++; - } + checked { count++; } } } @@ -176,15 +173,15 @@ public static long LongCount(this IEnumerable source, Func(this IEnumerable source, ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate); } - foreach (TSource element in source) + if (source.TryGetSpan(out ReadOnlySpan span)) { - if (predicate(element)) + foreach (TSource element in span) { - found = true; - return element; + if (predicate(element)) + { + found = true; + return element; + } + } + } + else + { + foreach (TSource element in source) + { + if (predicate(element)) + { + found = true; + return element; + } } } diff --git a/src/libraries/System.Linq/src/System/Linq/Single.cs b/src/libraries/System.Linq/src/System/Linq/Single.cs index 9c7328cc3ff16..830209369d5c4 100644 --- a/src/libraries/System.Linq/src/System/Linq/Single.cs +++ b/src/libraries/System.Linq/src/System/Linq/Single.cs @@ -117,8 +117,30 @@ public static TSource SingleOrDefault(this IEnumerable source, ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate); } - using (IEnumerator e = source.GetEnumerator()) + if (source.TryGetSpan(out ReadOnlySpan span)) { + for (int i = 0; i < span.Length; i++) + { + TSource result = span[i]; + if (predicate(result)) + { + for (i++; (uint)i < (uint)span.Length; i++) + { + if (predicate(span[i])) + { + ThrowHelper.ThrowMoreThanOneMatchException(); + } + } + + found = true; + return result; + } + } + } + else + { + using IEnumerator e = source.GetEnumerator(); + while (e.MoveNext()) { TSource result = e.Current;