Skip to content

Commit

Permalink
Reduce OrderBy overheads for default comparer (#76418)
Browse files Browse the repository at this point in the history
* 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<TKey>.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
  • Loading branch information
stephentoub authored Oct 3, 2022
1 parent ae4dc9c commit b8d4980
Showing 1 changed file with 59 additions and 5 deletions.
64 changes: 59 additions & 5 deletions src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace System.Linq
{
Expand Down Expand Up @@ -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)
Expand All @@ -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<TKey>.Default);
Debug.Assert(_next is null);
Debug.Assert(!_descending);

TKey[]? keys = _keys;
Debug.Assert(keys != null);

int c = Comparer<TKey>.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<TKey>.Default);
Debug.Assert(_next is null);
Debug.Assert(_descending);

TKey[]? keys = _keys;
Debug.Assert(keys != null);

int c = Comparer<TKey>.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<int>(keys, lo, hi - lo + 1).Sort(CompareAnyKeys);
protected override void QuickSort(int[] keys, int lo, int hi)
{
Comparison<int> comparison;

if (typeof(TKey).IsValueType && _next is null && _comparer == Comparer<TKey>.Default)
{
// We can use Comparer<TKey>.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<int>(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.
Expand Down

0 comments on commit b8d4980

Please sign in to comment.