Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve perf of Enumerable.Order{Descending} for primitives #76733

Merged
merged 2 commits into from Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 20 additions & 4 deletions src/libraries/System.Linq/src/System/Linq/OrderBy.cs
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace System.Linq
{
Expand All @@ -23,7 +24,7 @@ public static partial class Enumerable
/// This method compares elements by using the default comparer <see cref="Comparer{T}.Default"/>.
/// </remarks>
public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source) =>
OrderBy(source, EnumerableSorter<T>.IdentityFunc);
Order(source, comparer: null);

/// <summary>
/// Sorts the elements of a sequence in ascending order.
Expand All @@ -42,7 +43,9 @@ public static partial class Enumerable
/// If comparer is <see langword="null"/>, the default comparer <see cref="Comparer{T}.Default"/> is used to compare elements.
/// </remarks>
public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source, IComparer<T>? comparer) =>
OrderBy(source, EnumerableSorter<T>.IdentityFunc, comparer);
TypeIsImplicitlyStable<T>() && (comparer is null || comparer == Comparer<T>.Default) ?
new OrderedImplicitlyStableEnumerable<T>(source, descending: false) :
OrderBy(source, EnumerableSorter<T>.IdentityFunc, comparer);

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
=> new OrderedEnumerable<TSource, TKey>(source, keySelector, null, false, null);
Expand All @@ -66,7 +69,7 @@ public static partial class Enumerable
/// This method compares elements by using the default comparer <see cref="Comparer{T}.Default"/>.
/// </remarks>
public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source) =>
OrderByDescending(source, EnumerableSorter<T>.IdentityFunc);
OrderDescending(source, comparer: null);

/// <summary>
/// Sorts the elements of a sequence in descending order.
Expand All @@ -85,7 +88,9 @@ public static partial class Enumerable
/// If comparer is <see langword="null"/>, the default comparer <see cref="Comparer{T}.Default"/> is used to compare elements.
/// </remarks>
public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source, IComparer<T>? comparer) =>
OrderByDescending(source, EnumerableSorter<T>.IdentityFunc, comparer);
TypeIsImplicitlyStable<T>() && (comparer is null || comparer == Comparer<T>.Default) ?
new OrderedImplicitlyStableEnumerable<T>(source, descending: true) :
OrderByDescending(source, EnumerableSorter<T>.IdentityFunc, comparer);

public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) =>
new OrderedEnumerable<TSource, TKey>(source, keySelector, null, true, null);
Expand Down Expand Up @@ -132,6 +137,17 @@ public static partial class Enumerable

return source.CreateOrderedEnumerable(keySelector, comparer, true);
}

/// <summary>Gets whether the results of an unstable sort will be observably the same as a stable sort.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool TypeIsImplicitlyStable<T>() =>
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte) ||
typeof(T) == typeof(int) || typeof(T) == typeof(uint) ||
typeof(T) == typeof(short) || typeof(T) == typeof(ushort) ||
typeof(T) == typeof(long) || typeof(T) == typeof(ulong) ||
typeof(T) == typeof(Int128) || typeof(T) == typeof(UInt128) ||
typeof(T) == typeof(nint) || typeof(T) == typeof(nuint) ||
typeof(T) == typeof(bool) || typeof(T) == typeof(char);
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}

public interface IOrderedEnumerable<out TElement> : IEnumerable<TElement>
Expand Down
Expand Up @@ -3,13 +3,13 @@

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

namespace System.Linq
{
internal abstract partial class OrderedEnumerable<TElement> : IPartition<TElement>
{
public TElement[] ToArray()
public virtual TElement[] ToArray()
{
Buffer<TElement> buffer = new Buffer<TElement>(_source);

Expand All @@ -29,7 +29,7 @@ public TElement[] ToArray()
return array;
}

public List<TElement> ToList()
public virtual List<TElement> ToList()
{
Buffer<TElement> buffer = new Buffer<TElement>(_source);
int count = buffer._count;
Expand Down Expand Up @@ -247,4 +247,21 @@ private TElement Last(Buffer<TElement> buffer)
return value;
}
}

internal sealed partial class OrderedImplicitlyStableEnumerable<TElement> : OrderedEnumerable<TElement>
{
public override TElement[] ToArray()
{
TElement[] array = _source.ToArray();
Sort(array, _descending);
return array;
}

public override List<TElement> ToList()
{
List<TElement> list = _source.ToList();
Sort(CollectionsMarshal.AsSpan(list), _descending);
return list;
}
}
}
57 changes: 53 additions & 4 deletions src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.cs
Expand Up @@ -18,7 +18,7 @@ internal abstract partial class OrderedEnumerable<TElement> : IOrderedEnumerable
private int[] SortedMap(Buffer<TElement> buffer, int minIdx, int maxIdx) =>
GetEnumerableSorter().Sort(buffer._items, buffer._count, minIdx, maxIdx);

public IEnumerator<TElement> GetEnumerator()
public virtual IEnumerator<TElement> GetEnumerator()
{
Buffer<TElement> buffer = new Buffer<TElement>(_source);
if (buffer._count > 0)
Expand Down Expand Up @@ -62,9 +62,7 @@ internal IEnumerator<TElement> GetEnumerator(int minIdx, int maxIdx)

internal abstract EnumerableSorter<TElement> GetEnumerableSorter(EnumerableSorter<TElement>? next);

private CachingComparer<TElement> GetComparer() => GetComparer(null);

internal abstract CachingComparer<TElement> GetComparer(CachingComparer<TElement>? childComparer);
internal abstract CachingComparer<TElement> GetComparer(CachingComparer<TElement>? childComparer = null);

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

Expand Down Expand Up @@ -159,6 +157,57 @@ internal override CachingComparer<TElement> GetComparer(CachingComparer<TElement
}
}

/// <summary>An ordered enumerable used by Order/OrderDescending for Ts that are bitwise indistinguishable for any considered equal.</summary>
internal sealed partial class OrderedImplicitlyStableEnumerable<TElement> : OrderedEnumerable<TElement>
{
private readonly bool _descending;

public OrderedImplicitlyStableEnumerable(IEnumerable<TElement> source, bool descending) : base(source)
{
Debug.Assert(Enumerable.TypeIsImplicitlyStable<TElement>());

if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}

_descending = descending;
}

internal override CachingComparer<TElement> GetComparer(CachingComparer<TElement>? childComparer) =>
childComparer == null ?
new CachingComparer<TElement, TElement>(EnumerableSorter<TElement>.IdentityFunc, Comparer<TElement>.Default, _descending) :
new CachingComparerWithChild<TElement, TElement>(EnumerableSorter<TElement>.IdentityFunc, Comparer<TElement>.Default, _descending, childComparer);

internal override EnumerableSorter<TElement> GetEnumerableSorter(EnumerableSorter<TElement>? next) =>
new EnumerableSorter<TElement, TElement>(EnumerableSorter<TElement>.IdentityFunc, Comparer<TElement>.Default, _descending, next);

public override IEnumerator<TElement> GetEnumerator()
{
var buffer = new Buffer<TElement>(_source);
if (buffer._count > 0)
{
Sort(buffer._items.AsSpan(0, buffer._count), _descending);
for (int i = 0; i < buffer._count; i++)
{
yield return buffer._items[i];
}
}
}

private static void Sort(Span<TElement> span, bool descending)
{
if (descending)
{
span.Sort(static (a, b) => Comparer<TElement>.Default.Compare(b, a));
}
else
{
span.Sort();
}
}
}

// A comparer that chains comparisons, and pushes through the last element found to be
// lower or higher (depending on use), so as to represent the sort of comparisons
// done by OrderBy().ThenBy() combinations.
Expand Down
42 changes: 42 additions & 0 deletions src/libraries/System.Linq/tests/OrderDescendingTests.cs
Expand Up @@ -103,6 +103,48 @@ public void SourceReverseOfResultNullPassedAsComparer()
Assert.Equal(expected, source.OrderDescending(null));
}

[Fact]
public void OrderedDescendingToArray()
{
var source = new[]
{
5, 9, 6, 7, 8, 5, 20
};
var expected = new[]
{
20, 9, 8, 7, 6, 5, 5
};

Assert.Equal(expected, source.OrderDescending().ToArray());
}

[Fact]
public void EmptyOrderedDescendingToArray()
{
Assert.Empty(Enumerable.Empty<int>().OrderDescending().ToArray());
}

[Fact]
public void OrderedDescendingToList()
{
var source = new[]
{
5, 9, 6, 7, 8, 5, 20
};
var expected = new[]
{
20, 9, 8, 7, 6, 5, 5
};

Assert.Equal(expected, source.OrderDescending().ToList());
}

[Fact]
public void EmptyOrderedDescendingToList()
{
Assert.Empty(Enumerable.Empty<int>().OrderDescending().ToList());
}

[Fact]
public void SameKeysVerifySortStable()
{
Expand Down
8 changes: 8 additions & 0 deletions src/libraries/System.Linq/tests/OrderTests.cs
Expand Up @@ -485,5 +485,13 @@ public void CultureOrderElementAt()
}
}
}

[Fact]
public void StableSort_CustomComparerAlwaysReturns0()
{
byte[] values = new byte[] { 0x45, 0x7D, 0x4B, 0x61, 0x27 };
byte[] newValues = values.Order(Comparer<byte>.Create((a, b) => 0)).ToArray();
AssertExtensions.SequenceEqual(values, newValues);
}
}
}