-
Notifications
You must be signed in to change notification settings - Fork 4.9k
[WIP] Add Sort(...) extension methods for Span<T> #26859
Changes from 23 commits
92702fe
0c99da2
48f655d
57eb72f
2b8b08f
4272823
25fc3c6
22d819b
ba1da6a
ec77c64
4f500a3
4f2f822
758762b
fcb63b5
eff4ab4
0d99df1
03b1bef
84691a2
c8dff35
5352890
c109f6a
48be72d
7468485
78aa18c
07b0105
04c5c0b
ced09bd
4259560
9527a49
146029f
e44b8b6
596df18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,14 @@ | |
using Internal.Runtime.CompilerServices; | ||
#endif | ||
|
||
using SHC = System.SpanSortHelpersCommon; | ||
// Consolidated code | ||
//using SHK = System.SpanSortHelpersKeysAndOrValues; | ||
//using SHKV = System.SpanSortHelpersKeysAndOrValues; | ||
// Specialized for either only keys or keys and values and for comparable or not | ||
using SHK = System.SpanSortHelpersKeys; | ||
using SHKV = System.SpanSortHelpersKeysValues; | ||
|
||
namespace System | ||
{ | ||
/// <summary> | ||
|
@@ -922,5 +930,89 @@ public static int BinarySearch<T, TComparer>( | |
value, comparer); | ||
return BinarySearch(span, comparable); | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Sorts the elements in the entire <see cref="Span{T}" /> | ||
/// using the <see cref="IComparable" /> implementation of each | ||
/// element of the <see cref= "Span{T}" /> | ||
/// </summary> | ||
/// <param name="span">The <see cref="Span{T}"/> to sort.</param> | ||
/// <exception cref = "InvalidOperationException"> | ||
/// One or more elements do not implement the <see cref="IComparable" /> interface. | ||
/// </exception> | ||
// TODO: Revise exception list, if we do not try/catch | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void Sort<T>(this Span<T> span) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: use abbreviated syntax, here and elsewhere public static void Sort<T>(this Span<T> span) => SHK.Sort(span); |
||
{ | ||
SHK.Sort(span); | ||
} | ||
|
||
/// <summary> | ||
/// Sorts the elements in the entire <see cref="Span{T}" /> | ||
/// using the <typeparamref name="TComparer" />. | ||
/// </summary> | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void Sort<T, TComparer>(this Span<T> span, TComparer comparer) | ||
where TComparer : IComparer<T> | ||
{ | ||
SHK.Sort(span, comparer); | ||
} | ||
|
||
/// <summary> | ||
/// Sorts the elements in the entire <see cref="Span{T}" /> | ||
/// using the <see cref="Comparison{T}" />. | ||
/// </summary> | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void Sort<T>(this Span<T> span, Comparison<T> comparison) | ||
{ | ||
if (comparison == null) | ||
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparison); | ||
|
||
SHK.Sort(span, new SHC.ComparisonComparer<T>(comparison)); | ||
} | ||
|
||
/// <summary> | ||
/// Sorts a pair of spans | ||
/// (one contains the keys <see cref="Span{TKey}"/> | ||
/// and the other contains the corresponding items <see cref="Span{TValue}"/>) | ||
/// based on the keys in the first <see cref= "Span{TKey}" /> | ||
/// using the <see cref="IComparable" /> implementation of each | ||
/// element of the <see cref= "Span{TKey}"/>. | ||
/// </summary> | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void Sort<TKey, TValue>(this Span<TKey> keys, Span<TValue> items) | ||
{ | ||
SHKV.Sort(keys, items); | ||
} | ||
|
||
/// <summary> | ||
/// Sorts a pair of spans | ||
/// (one contains the keys <see cref="Span{TKey}"/> | ||
/// and the other contains the corresponding items <see cref="Span{TValue}"/>) | ||
/// based on the keys in the first <see cref= "Span{TKey}" /> | ||
/// using the <typeparamref name="TComparer" />. | ||
/// </summary> | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void Sort<TKey, TValue, TComparer>(this Span<TKey> keys, | ||
Span<TValue> items, TComparer comparer) | ||
where TComparer : IComparer<TKey> | ||
{ | ||
SHKV.Sort(keys, items, comparer); | ||
} | ||
|
||
/// <summary> | ||
/// Sorts a pair of spans | ||
/// (one contains the keys <see cref="Span{TKey}"/> | ||
/// and the other contains the corresponding items <see cref="Span{TValue}"/>) | ||
/// based on the keys in the first <see cref= "Span{TKey}" /> | ||
/// using the <see cref="Comparison{TKey}" />. | ||
/// </summary> | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void Sort<TKey, TValue>(this Span<TKey> keys, | ||
Span<TValue> items, Comparison<TKey> comparison) | ||
{ | ||
SHKV.Sort(keys, items, new SHC.ComparisonComparer<TKey>(comparison)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Runtime.CompilerServices; | ||
|
||
#if !netstandard | ||
using Internal.Runtime.CompilerServices; | ||
#endif | ||
|
||
namespace System | ||
{ | ||
// TODO: Rename to SpanSortHelpers before move to corefx | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove TODO? |
||
internal static partial class SpanSortHelpersCommon | ||
{ | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: remove new line |
||
// This is the threshold where Introspective sort switches to Insertion sort. | ||
// Empirically, 16 seems to speed up most cases without slowing down others, at least for integers. | ||
// Large value types may benefit from a smaller number. | ||
internal const int IntrosortSizeThreshold = 16; | ||
|
||
internal static int FloorLog2PlusOne(int n) | ||
{ | ||
Debug.Assert(n >= 2); | ||
int result = 2; | ||
n >>= 2; | ||
while (n > 0) | ||
{ | ||
++result; | ||
n >>= 1; | ||
} | ||
return result; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static void Swap<T>(ref T items, int i, int j) | ||
{ | ||
Debug.Assert(i != j); | ||
Swap(ref Unsafe.Add(ref items, i), ref Unsafe.Add(ref items, j)); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static void Swap<T>(ref T a, ref T b) | ||
{ | ||
T temp = a; | ||
a = b; | ||
b = temp; | ||
} | ||
|
||
// This started out with just LessThan. | ||
// However, due to bogus comparers, comparables etc. | ||
// we need preserve semantics completely to get same result. | ||
internal interface IDirectComparer<in T> | ||
{ | ||
bool GreaterThan(T x, T y); | ||
bool LessThan(T x, T y); | ||
bool LessThanEqual(T x, T y); // TODO: Delete if we are not doing specialize Sort3 | ||
} | ||
// | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: add new line |
||
// Type specific DirectComparer(s) to ensure optimal code-gen | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these type-specific structs really necessary for optimal code-gen? cc @AndyAyersMS There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nietras can you compare perf with and without these extra comparers, and if it's quite different, show the key bits of generated code? Also there still seems to be a healthy dose of Also (not necessarily critiquing this change, just wondering aloud) it seems like the same algorithmic patterns are duplicated across different comparison contexts, and I wonder if there is some way to avoid this, or whether we are missing language features and/or concepts that would allow there to be one master version that gets appropriately specialized and optimized, or whether this is just how things are done for now. I know we haven't put the effort into optimizing general comparers the way we have for equality comparers -- maybe we should? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AndyAyersMS perf is significantly lower if using So yes, these are necessary, and I would really like for perf to be good on existing runtimes. |
||
// | ||
internal struct SByteDirectComparer : IDirectComparer<sbyte> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are all these AggressiveInlining attributes necessary? I think we should remove them and only use them sparingly where it really matters and we know that the JIT can't do it. At least, that is the guideline I have been given. cc @jkotas, @AndyAyersMS There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ahsonkhan I'll await feedback before deleting them, but deleting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My guess would be that most of these attributes are not needed -- if a method simply invokes another method it is quite likely the jit will inline it without the attribute. Overall guidance is that you should consider adding the
In particular you need to be careful when using it in combination with generics as the runtime can create large numbers of generic instantiations and the benefits of inlining are often type specific. |
||
public bool GreaterThan(sbyte x, sbyte y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(sbyte x, sbyte y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(sbyte x, sbyte y) => x <= y; | ||
} | ||
internal struct ByteDirectComparer : IDirectComparer<byte> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(byte x, byte y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(byte x, byte y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(byte x, byte y) => x <= y; | ||
} | ||
internal struct Int16DirectComparer : IDirectComparer<short> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(short x, short y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(short x, short y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(short x, short y) => x <= y; | ||
} | ||
internal struct UInt16DirectComparer : IDirectComparer<ushort> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(ushort x, ushort y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(ushort x, ushort y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(ushort x, ushort y) => x <= y; | ||
} | ||
internal struct Int32DirectComparer : IDirectComparer<int> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(int x, int y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(int x, int y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(int x, int y) => x <= y; | ||
} | ||
internal struct UInt32DirectComparer : IDirectComparer<uint> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(uint x, uint y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(uint x, uint y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(uint x, uint y) => x <= y; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: re-use GreaterThan, i.e. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deleting this since not needed, but in general I would have kept this as is for optimal code-gen |
||
} | ||
internal struct Int64DirectComparer : IDirectComparer<long> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(long x, long y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(long x, long y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(long x, long y) => x <= y; | ||
} | ||
internal struct UInt64DirectComparer : IDirectComparer<ulong> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(ulong x, ulong y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(ulong x, ulong y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(ulong x, ulong y) => x <= y; | ||
} | ||
internal struct SingleDirectComparer : IDirectComparer<float> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(float x, float y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(float x, float y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(float x, float y) => x <= y; | ||
} | ||
internal struct DoubleDirectComparer : IDirectComparer<double> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(double x, double y) => x > y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(double x, double y) => x < y; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(double x, double y) => x <= y; | ||
} | ||
// TODO: Revise whether this is needed | ||
internal struct StringDirectComparer : IDirectComparer<string> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(string x, string y) => x.CompareTo(y) > 0; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(string x, string y) => x.CompareTo(y) < 0; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(string x, string y) => x.CompareTo(y) <= 0; | ||
} | ||
|
||
// Helper to allow sharing code | ||
// Does not work well for reference types | ||
internal struct ComparerDirectComparer<T, TComparer> : IDirectComparer<T> | ||
where TComparer : IComparer<T> | ||
{ | ||
readonly TComparer _comparer; | ||
|
||
public ComparerDirectComparer(TComparer comparer) | ||
{ | ||
_comparer = comparer; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(T x, T y) => _comparer.Compare(x, y) > 0; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(T x, T y) => _comparer.Compare(x, y) < 0; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(T x, T y) => _comparer.Compare(x, y) <= 0; | ||
} | ||
// Helper to allow sharing code | ||
// Does not work well for reference types | ||
internal struct ComparableDirectComparer<T> : IDirectComparer<T> | ||
where T : IComparable<T> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool GreaterThan(T x, T y) => x.CompareTo(y) > 0; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThan(T x, T y) => x.CompareTo(y) < 0; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool LessThanEqual(T x, T y) => x.CompareTo(y) <= 0; | ||
} | ||
|
||
// Helper to allow sharing code (TODO: This probably has issues for reference types...) | ||
internal struct ComparisonComparer<T> : IComparer<T> | ||
{ | ||
readonly Comparison<T> m_comparison; | ||
|
||
public ComparisonComparer(Comparison<T> comparison) | ||
{ | ||
m_comparison = comparison; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public int Compare(T x, T y) => m_comparison(x, y); | ||
} | ||
|
||
|
||
internal interface IIsNaN<T> | ||
{ | ||
bool IsNaN(T value); | ||
} | ||
internal struct SingleIsNaN : IIsNaN<float> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool IsNaN(float value) => float.IsNaN(value); | ||
} | ||
internal struct DoubleIsNaN : IIsNaN<double> | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool IsNaN(double value) => double.IsNaN(value); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice: new line.
Generate the reference using our tool.
Run
msbuild /t:GenerateReferenceSource
from the S.Memory source directory.cc @weshaggard if you have any questions.