From b8d49801fe03b96d2fead3d97a11dce1e723dd17 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 2 Oct 2022 22:18:04 -0400 Subject: [PATCH] Reduce OrderBy overheads for default comparer (#76418) * Reduce OrderBy overheads for default comparer If OrderBy is used with a value type key and and a default or no comparer is specified, we can use `Comparer.Default.Compare` to avoid the interface dispatch. This optimization can be taken further, but for now it handles a common set of cases. * Address PR feedback --- .../src/System/Linq/OrderedEnumerable.cs | 64 +++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs index 51444ae177382..36f6b992712c3 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; namespace System.Linq { @@ -340,9 +339,10 @@ internal override void ComputeKeys(TElement[] elements, int count) internal override int CompareAnyKeys(int index1, int index2) { - Debug.Assert(_keys != null); + TKey[]? keys = _keys; + Debug.Assert(keys != null); - int c = _comparer.Compare(_keys[index1], _keys[index2]); + int c = _comparer.Compare(keys[index1], keys[index2]); if (c == 0) { if (_next == null) @@ -359,10 +359,64 @@ internal override int CompareAnyKeys(int index1, int index2) return (_descending != (c > 0)) ? 1 : -1; } + private int CompareAnyKeys_DefaultComparer_NoNext_Ascending(int index1, int index2) + { + Debug.Assert(typeof(TKey).IsValueType); + Debug.Assert(_comparer == Comparer.Default); + Debug.Assert(_next is null); + Debug.Assert(!_descending); + + TKey[]? keys = _keys; + Debug.Assert(keys != null); + + int c = Comparer.Default.Compare(keys[index1], keys[index2]); + return + c == 0 ? index1 - index2 : // ensure stability of sort + c; + } + + private int CompareAnyKeys_DefaultComparer_NoNext_Descending(int index1, int index2) + { + Debug.Assert(typeof(TKey).IsValueType); + Debug.Assert(_comparer == Comparer.Default); + Debug.Assert(_next is null); + Debug.Assert(_descending); + + TKey[]? keys = _keys; + Debug.Assert(keys != null); + + int c = Comparer.Default.Compare(keys[index2], keys[index1]); + return + c == 0 ? index1 - index2 : // ensure stability of sort + c; + } + private int CompareKeys(int index1, int index2) => index1 == index2 ? 0 : CompareAnyKeys(index1, index2); - protected override void QuickSort(int[] keys, int lo, int hi) => - new Span(keys, lo, hi - lo + 1).Sort(CompareAnyKeys); + protected override void QuickSort(int[] keys, int lo, int hi) + { + Comparison comparison; + + if (typeof(TKey).IsValueType && _next is null && _comparer == Comparer.Default) + { + // We can use Comparer.Default.Compare and benefit from devirtualization and inlining. + // We can also avoid extra steps to check whether we need to deal with a subsequent tie breaker (_next). + if (!_descending) + { + comparison = CompareAnyKeys_DefaultComparer_NoNext_Ascending; + } + else + { + comparison = CompareAnyKeys_DefaultComparer_NoNext_Descending; + } + } + else + { + comparison = CompareAnyKeys; + } + + new Span(keys, lo, hi - lo + 1).Sort(comparison); + } // Sorts the k elements between minIdx and maxIdx without sorting all elements // Time complexity: O(n + k log k) best and average case. O(n^2) worse case.