From 15a63951ccd0ba3ba1b8c345331d1f960c4d2efa Mon Sep 17 00:00:00 2001 From: nietras Date: Wed, 14 Mar 2018 11:56:46 +0100 Subject: [PATCH 1/8] Add span sort code. Missing throwhelper stuff. --- .../System.Private.CoreLib.Shared.projitems | 13 + .../shared/System/MemoryExtensions.cs | 85 +++++ .../shared/System/SpanSortHelpers.Common.cs | 175 +++++++++ .../System/SpanSortHelpers.Keys.Comparison.cs | 329 +++++++++++++++++ .../SpanSortHelpers.Keys.IComparable.cs | 312 ++++++++++++++++ .../SpanSortHelpers.Keys.Specialized.cs | 144 ++++++++ .../System/SpanSortHelpers.Keys.TComparer.cs | 334 +++++++++++++++++ .../SpanSortHelpers.Keys.TDirectComparer.cs | 328 +++++++++++++++++ .../shared/System/SpanSortHelpers.Keys.cs | 234 ++++++++++++ .../SpanSortHelpers.KeysValues.Comparison.cs | 284 ++++++++++++++ .../SpanSortHelpers.KeysValues.IComparable.cs | 347 ++++++++++++++++++ .../SpanSortHelpers.KeysValues.Specialized.cs | 175 +++++++++ .../SpanSortHelpers.KeysValues.TComparer.cs | 288 +++++++++++++++ ...nSortHelpers.KeysValues.TDirectComparer.cs | 281 ++++++++++++++ .../System/SpanSortHelpers.KeysValues.cs | 243 ++++++++++++ 15 files changed, 3572 insertions(+) create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.Common.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.Keys.Comparison.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.Keys.IComparable.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.Keys.Specialized.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.Keys.TComparer.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.Keys.TDirectComparer.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.Keys.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Comparison.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.KeysValues.IComparable.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Specialized.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TComparer.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TDirectComparer.cs create mode 100644 src/mscorlib/shared/System/SpanSortHelpers.KeysValues.cs diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index a5b49c8cff90..f2b526856303 100644 --- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -491,6 +491,19 @@ + + + + + + + + + + + + + diff --git a/src/mscorlib/shared/System/MemoryExtensions.cs b/src/mscorlib/shared/System/MemoryExtensions.cs index 4f278b634028..008f5def32da 100644 --- a/src/mscorlib/shared/System/MemoryExtensions.cs +++ b/src/mscorlib/shared/System/MemoryExtensions.cs @@ -1304,5 +1304,90 @@ public static int BinarySearch( value, comparer); return BinarySearch(span, comparable); } + + /// + /// Sorts the elements in the entire + /// using the implementation of each + /// element of the + /// + /// The to sort. + /// + /// One or more elements do not implement the interface. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Sort(this Span span) + { + SpanSortHelpersKeys.Sort(span); + } + + /// + /// Sorts the elements in the entire + /// using the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Sort(this Span span, TComparer comparer) + where TComparer : IComparer + { + SpanSortHelpersKeys.Sort(span, comparer); + } + + /// + /// Sorts the elements in the entire + /// using the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Sort(this Span span, Comparison comparison) + { + if (comparison == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparison); + + SpanSortHelpersKeys.Sort(span, comparison); + } + + /// + /// Sorts a pair of spans + /// (one contains the keys + /// and the other contains the corresponding items ) + /// based on the keys in the first + /// using the implementation of each + /// element of the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Sort(this Span keys, Span items) + { + SpanSortHelpersKeysValues.Sort(keys, items); + } + + /// + /// Sorts a pair of spans + /// (one contains the keys + /// and the other contains the corresponding items ) + /// based on the keys in the first + /// using the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Sort(this Span keys, + Span items, TComparer comparer) + where TComparer : IComparer + { + SpanSortHelpersKeysValues.Sort(keys, items, comparer); + } + + /// + /// Sorts a pair of spans + /// (one contains the keys + /// and the other contains the corresponding items ) + /// based on the keys in the first + /// using the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Sort(this Span keys, + Span items, Comparison comparison) + { + if (comparison == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparison); + + SpanSortHelpersKeysValues.Sort(keys, items, comparison); + } } } diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Common.cs b/src/mscorlib/shared/System/SpanSortHelpers.Common.cs new file mode 100644 index 000000000000..edeffa2c2b79 --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.Common.cs @@ -0,0 +1,175 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +namespace System +{ + // TODO: Rename to SpanSortHelpers before move to corefx + internal static partial class SpanSortHelpersCommon + { + + // 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(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(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 to preserve semantics completely to get same result. + internal interface IDirectComparer + { + 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 + } + // + // Type specific DirectComparer(s) to ensure optimal code-gen + // + internal struct SByteDirectComparer : IDirectComparer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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 + { + [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 + { + [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 + { + [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 + { + [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 + { + [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; + } + internal struct Int64DirectComparer : IDirectComparer + { + [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 + { + [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 + { + [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 + { + [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 + { + [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; + } + + internal interface IIsNaN + { + bool IsNaN(T value); + } + internal struct SingleIsNaN : IIsNaN + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNaN(float value) => float.IsNaN(value); + } + internal struct DoubleIsNaN : IIsNaN + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNaN(double value) => double.IsNaN(value); + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.Comparison.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.Comparison.cs new file mode 100644 index 000000000000..a5534546bb67 --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.Comparison.cs @@ -0,0 +1,329 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeys + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + ref TKey keys, int length, + Comparison comparison) + { + IntrospectiveSort(ref keys, length, comparison); + } + + private static void IntrospectiveSort( + ref TKey keys, int length, + Comparison comparison) + { + var depthLimit = 2 * FloorLog2PlusOne(length); + IntroSort(ref keys, 0, length - 1, depthLimit, comparison); + } + + private static void IntroSort( + ref TKey keys, + int lo, int hi, int depthLimit, + Comparison comparison) + { + Debug.Assert(comparison != null); + Debug.Assert(lo >= 0); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + Sort2(ref keys, lo, hi, comparison); + return; + } + if (partitionSize == 3) + { + ref TKey loRef = ref Unsafe.Add(ref keys, lo); + ref TKey miRef = ref Unsafe.Add(ref keys, hi - 1); + ref TKey hiRef = ref Unsafe.Add(ref keys, hi); + //ref TKey miRef = ref Unsafe.SubtractByteOffset(ref hiRef, new IntPtr(Unsafe.SizeOf())); + Sort3(ref loRef, ref miRef, ref hiRef, comparison); + return; + } + + InsertionSort(ref keys, lo, hi, comparison); + return; + } + + if (depthLimit == 0) + { + HeapSort(ref keys, lo, hi, comparison); + return; + } + depthLimit--; + + // We should never reach here, unless > 3 elements due to partition size + int p = PickPivotAndPartition(ref keys, lo, hi, comparison); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(ref keys, p + 1, hi, depthLimit, comparison); + hi = p - 1; + } + } + + private static int PickPivotAndPartition( + ref TKey keys, int lo, int hi, + Comparison comparison) + { + Debug.Assert(comparison != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + // Compute median-of-three. But also partition them, since we've done the comparison. + + // PERF: `lo` or `hi` will never be negative inside the loop, + // so computing median using uints is safe since we know + // `length <= int.MaxValue`, and indices are >= 0 + // and thus cannot overflow an uint. + // Saves one subtraction per loop compared to + // `int middle = lo + ((hi - lo) >> 1);` + int middle = (int)(((uint)hi + (uint)lo) >> 1); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtMiddle = ref Unsafe.Add(ref keys, middle); + ref TKey keysAtHi = ref Unsafe.Add(ref keys, hi); + Sort3(ref keysAtLo, ref keysAtMiddle, ref keysAtHi, comparison); + + TKey pivot = keysAtMiddle; + + int left = lo; + int right = hi - 1; + // We already partitioned lo and hi and put the pivot in hi - 1. + // And we pre-increment & decrement below. + Swap(ref keysAtMiddle, ref Unsafe.Add(ref keys, right)); + + while (left < right) + { + // TODO: Would be good to be able to update local ref here + + while (left < (hi - 1) && comparison(Unsafe.Add(ref keys, ++left), pivot) < 0) ; + // Check if bad comparable/comparison + if (left == (hi - 1) && comparison(Unsafe.Add(ref keys, left), pivot) < 0) + ThrowHelper.ThrowArgumentException_BadComparer(comparison); + + while (right > lo && comparison(pivot, Unsafe.Add(ref keys, --right)) < 0) ; + // Check if bad comparable/comparison + if (right == lo && comparison(pivot, Unsafe.Add(ref keys, right)) < 0) + ThrowHelper.ThrowArgumentException_BadComparer(comparison); + + if (left >= right) + break; + + Swap(ref keys, left, right); + } + // Put pivot in the right location. + right = hi - 1; + if (left != right) + { + Swap(ref keys, left, right); + } + return left; + } + + private static void HeapSort( + ref TKey keys, int lo, int hi, + Comparison comparison) + + { + Debug.Assert(comparison != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; --i) + { + DownHeap(ref keys, i, n, lo, comparison); + } + for (int i = n; i > 1; --i) + { + Swap(ref keys, lo, lo + i - 1); + DownHeap(ref keys, 1, i - 1, lo, comparison); + } + } + + private static void DownHeap( + ref TKey keys, int i, int n, int lo, + Comparison comparison) + + { + Debug.Assert(comparison != null); + Debug.Assert(lo >= 0); + + //TKey d = keys[lo + i - 1]; + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + TKey d = Unsafe.Add(ref keysAtLoMinus1, i); + var nHalf = n / 2; + while (i <= nHalf) + { + int child = i << 1; + + //if (child < n && comparison(keys[lo + child - 1], keys[lo + child]) < 0) + if (child < n && + comparison(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child)) < 0) + { + ++child; + } + + //if (!(comparison(d, keys[lo + child - 1]) < 0)) + if (!(comparison(d, Unsafe.Add(ref keysAtLoMinus1, child)) < 0)) + break; + + // keys[lo + i - 1] = keys[lo + child - 1] + Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); + + i = child; + } + //keys[lo + i - 1] = d; + Unsafe.Add(ref keysAtLoMinus1, i) = d; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InsertionSort( + ref TKey keys, int lo, int hi, + Comparison comparison) + + { + Debug.Assert(lo >= 0); + Debug.Assert(hi >= lo); + + for (int i = lo; i < hi; ++i) + { + int j = i; + //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); + // TODO: Would be good to be able to update local ref here + if (j >= lo && comparison(t, Unsafe.Add(ref keys, j)) < 0) + { + do + { + Unsafe.Add(ref keys, j + 1) = Unsafe.Add(ref keys, j); + --j; + } + while (j >= lo && comparison(t, Unsafe.Add(ref keys, j)) < 0); + + Unsafe.Add(ref keys, j + 1) = t; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort3( + ref TKey r0, ref TKey r1, ref TKey r2, + Comparison comparison) + + { + Sort2(ref r0, ref r1, comparison); + Sort2(ref r0, ref r2, comparison); + Sort2(ref r1, ref r2, comparison); + + // Below works but does not give exactly the same result as Array.Sort + // i.e. order could be a bit different for keys that are equal + //if (comparison.LessThanEqual(r0, r1)) + //{ + // // r0 <= r1 + // if (comparison.LessThanEqual(r1, r2)) + // { + // // r0 <= r1 <= r2 + // return; // Is this return good or bad for perf? + // } + // // r0 <= r1 + // // r2 < r1 + // else if (comparison.LessThanEqual(r0, r2)) + // { + // // r0 <= r2 < r1 + // Swap(ref r1, ref r2); + // } + // // r0 <= r1 + // // r2 < r1 + // // r2 < r0 + // else + // { + // // r2 < r0 <= r1 + // TKey tmp = r0; + // r0 = r2; + // r2 = r1; + // r1 = tmp; + // } + //} + //else + //{ + // // r1 < r0 + // if (comparison.LessThan(r2, r1)) + // { + // // r2 < r1 < r0 + // Swap(ref r0, ref r2); + // } + // // r1 < r0 + // // r1 <= r2 + // else if (comparison.LessThan(r2, r0)) + // { + // // r1 <= r2 < r0 + // TKey tmp = r0; + // r0 = r1; + // r1 = r2; + // r2 = tmp; + // } + // // r1 < r0 + // // r1 <= r2 + // // r0 <= r2 + // else + // { + // // r1 < r0 <= r2 + // Swap(ref r0, ref r1); + // } + //} + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey keys, int i, int j, + Comparison comparison) + + { + Debug.Assert(i != j); + + ref TKey a = ref Unsafe.Add(ref keys, i); + ref TKey b = ref Unsafe.Add(ref keys, j); + Sort2(ref a, ref b, comparison); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey a, ref TKey b, Comparison comparison) + + { + // This is one of the only places GreaterThan is needed + // but we need to preserve this due to bogus comparers or similar + if (comparison(a, b) > 0) + { + TKey temp = a; + a = b; + b = temp; + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.IComparable.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.IComparable.cs new file mode 100644 index 000000000000..8c18d5171ebd --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.IComparable.cs @@ -0,0 +1,312 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeys + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + ref TKey keys, int length) + where TKey : IComparable + { + IntrospectiveSort(ref keys, length); + } + + private static void IntrospectiveSort( + ref TKey keys, int length) + where TKey : IComparable + { + var depthLimit = 2 * FloorLog2PlusOne(length); + IntroSort(ref keys, 0, length - 1, depthLimit); + } + + private static void IntroSort( + ref TKey keys, + int lo, int hi, int depthLimit) + where TKey : IComparable + { + Debug.Assert(lo >= 0); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + Sort2(ref keys, lo, hi); + return; + } + if (partitionSize == 3) + { + ref TKey loRef = ref Unsafe.Add(ref keys, lo); + ref TKey miRef = ref Unsafe.Add(ref keys, hi - 1); + ref TKey hiRef = ref Unsafe.Add(ref keys, hi); + //ref TKey miRef = ref Unsafe.SubtractByteOffset(ref hiRef, new IntPtr(Unsafe.SizeOf())); + Sort3(ref loRef, ref miRef, ref hiRef); + return; + } + + InsertionSort(ref keys, lo, hi); + return; + } + + if (depthLimit == 0) + { + HeapSort(ref keys, lo, hi); + return; + } + depthLimit--; + + // We should never reach here, unless > 3 elements due to partition size + int p = PickPivotAndPartition(ref keys, lo, hi); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(ref keys, p + 1, hi, depthLimit); + hi = p - 1; + } + } + + private static int PickPivotAndPartition( + ref TKey keys, int lo, int hi) + where TKey : IComparable + { + + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + // Compute median-of-three. But also partition them, since we've done the comparison. + + // PERF: `lo` or `hi` will never be negative inside the loop, + // so computing median using uints is safe since we know + // `length <= int.MaxValue`, and indices are >= 0 + // and thus cannot overflow an uint. + // Saves one subtraction per loop compared to + // `int middle = lo + ((hi - lo) >> 1);` + int middle = (int)(((uint)hi + (uint)lo) >> 1); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtMiddle = ref Unsafe.Add(ref keys, middle); + ref TKey keysAtHi = ref Unsafe.Add(ref keys, hi); + Sort3(ref keysAtLo, ref keysAtMiddle, ref keysAtHi); + + TKey pivot = keysAtMiddle; + + int left = lo; + int right = hi - 1; + // We already partitioned lo and hi and put the pivot in hi - 1. + // And we pre-increment & decrement below. + Swap(ref keysAtMiddle, ref Unsafe.Add(ref keys, right)); + + while (left < right) + { + // TODO: Would be good to be able to update local ref here + + if (pivot == null) + { + while (left < (hi - 1) && Unsafe.Add(ref keys, ++left) == null) ; + while (right > lo && Unsafe.Add(ref keys, --right) != null) ; + } + else + { + while (left < (hi - 1) && pivot.CompareTo(Unsafe.Add(ref keys, ++left)) > 0) ; + // Check if bad comparable/comparer + if (left == (hi - 1) && pivot.CompareTo(Unsafe.Add(ref keys, left)) > 0) + ThrowHelper.ThrowArgumentException_BadComparable(typeof(TKey)); + + while (right > lo && pivot.CompareTo(Unsafe.Add(ref keys, --right)) < 0) ; + // Check if bad comparable/comparer + if (right == lo && pivot.CompareTo(Unsafe.Add(ref keys, right)) < 0) + ThrowHelper.ThrowArgumentException_BadComparable(typeof(TKey)); + } + + if (left >= right) + break; + + Swap(ref keys, left, right); + } + // Put pivot in the right location. + right = hi - 1; + if (left != right) + { + Swap(ref keys, left, right); + } + return left; + } + + private static void HeapSort( + ref TKey keys, int lo, int hi) + where TKey : IComparable + { + + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; --i) + { + DownHeap(ref keys, i, n, lo); + } + for (int i = n; i > 1; --i) + { + Swap(ref keys, lo, lo + i - 1); + DownHeap(ref keys, 1, i - 1, lo); + } + } + + private static void DownHeap( + ref TKey keys, int i, int n, int lo) + where TKey : IComparable + { + + Debug.Assert(lo >= 0); + + //TKey d = keys[lo + i - 1]; + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + TKey d = Unsafe.Add(ref keysAtLoMinus1, i); + var nHalf = n / 2; + while (i <= nHalf) + { + int child = i << 1; + + //if (child < n && (keys[lo + child - 1] == null || keys[lo + child - 1].CompareTo(keys[lo + child]) < 0)) + if (child < n && + (Unsafe.Add(ref keysAtLoMinus1, child) == null || + Unsafe.Add(ref keysAtLoMinus1, child).CompareTo(Unsafe.Add(ref keysAtLo, child)) < 0)) + { + ++child; + } + + //if (keys[lo + child - 1] == null || keys[lo + child - 1].CompareTo(d) < 0) + if (Unsafe.Add(ref keysAtLoMinus1, child) == null || + Unsafe.Add(ref keysAtLoMinus1, child).CompareTo(d) < 0) + break; + + // keys[lo + i - 1] = keys[lo + child - 1] + Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); + + i = child; + } + //keys[lo + i - 1] = d; + Unsafe.Add(ref keysAtLoMinus1, i) = d; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InsertionSort( + ref TKey keys, int lo, int hi) + where TKey : IComparable + { + Debug.Assert(lo >= 0); + Debug.Assert(hi >= lo); + + for (int i = lo; i < hi; ++i) + { + int j = i; + //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); + // TODO: Would be good to be able to update local ref here + if (j >= lo && (t == null || t.CompareTo(Unsafe.Add(ref keys, j)) < 0)) + { + do + { + Unsafe.Add(ref keys, j + 1) = Unsafe.Add(ref keys, j); + --j; + } + while (j >= lo && (t == null || t.CompareTo(Unsafe.Add(ref keys, j)) < 0)); + //while (j >= lo && (t == null || t.CompareTo(keys[j]) < 0)) + + Unsafe.Add(ref keys, j + 1) = t; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort3( + ref TKey r0, ref TKey r1, ref TKey r2) + where TKey : IComparable + { + Sort2(ref r0, ref r1); + Sort2(ref r0, ref r2); + Sort2(ref r1, ref r2); + + // Below works but does not give exactly the same result as Array.Sort + // i.e. order could be a bit different for keys that are equal + //if (r0 != null && r0.CompareTo(r1) <= 0) //r0 <= r1) + //{ + // if (r1 != null && r1.CompareTo(r2) <= 0) //(r1 <= r2) + // { + // return; + // } + // else if (r0.CompareTo(r2) < 0) //(r0 < r2) + // { + // Swap(ref r1, ref r2); + // } + // else + // { + // TKey tmp = r0; + // r0 = r2; + // r2 = r1; + // r1 = tmp; + // } + //} + //else + //{ + // if (r0 != null && r0.CompareTo(r2) < 0) //(r0 < r2) + // { + // Swap(ref r0, ref r1); + // } + // else if (r2 != null && r2.CompareTo(r1) < 0) //(r2 < r1) + // { + // Swap(ref r0, ref r2); + // } + // else + // { + // TKey tmp = r0; + // r0 = r1; + // r1 = r2; + // r2 = tmp; + // } + //} + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey keys, int i, int j) + where TKey : IComparable + { + Debug.Assert(i != j); + + ref TKey a = ref Unsafe.Add(ref keys, i); + ref TKey b = ref Unsafe.Add(ref keys, j); + Sort2(ref a, ref b); + } + + private static void Sort2(ref TKey a, ref TKey b) + where TKey : IComparable + { + if (a != null && a.CompareTo(b) > 0) + { + TKey temp = a; + a = b; + b = temp; + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.Specialized.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.Specialized.cs new file mode 100644 index 000000000000..03be11a7a31e --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.Specialized.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeys_DirectComparer + { + // https://github.com/dotnet/coreclr/blob/master/src/classlibnative/bcltype/arrayhelpers.cpp + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TrySortSpecialized( + ref TKey keys, int length) + { + // Type unfolding adopted from https://github.com/dotnet/coreclr/blob/master/src/classlibnative/bcltype/arrayhelpers.cpp#L268 + if (typeof(TKey) == typeof(sbyte)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, length, new SByteDirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(byte) || + typeof(TKey) == typeof(bool)) // Use byte for bools to reduce code size + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, length, new ByteDirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(short)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, length, new Int16DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(ushort) || + typeof(TKey) == typeof(char)) // Use ushort for chars to reduce code size) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, length, new UInt16DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(int)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, length, new Int32DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(uint)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, length, new UInt32DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(long)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, length, new Int64DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(ulong)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, length, new UInt64DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(float)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + + // Comparison to NaN is always false, so do a linear pass + // and swap all NaNs to the front of the array + var left = NaNPrepass(ref specificKeys, length, new SingleIsNaN()); + + var remaining = length - left; + if (remaining > 1) + { + ref var afterNaNsKeys = ref Unsafe.Add(ref specificKeys, left); + Sort(ref afterNaNsKeys, remaining, new SingleDirectComparer()); + } + return true; + } + else if (typeof(TKey) == typeof(double)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + + // Comparison to NaN is always false, so do a linear pass + // and swap all NaNs to the front of the array + var left = NaNPrepass(ref specificKeys, length, new DoubleIsNaN()); + var remaining = length - left; + if (remaining > 1) + { + ref var afterNaNsKeys = ref Unsafe.Add(ref specificKeys, left); + Sort(ref afterNaNsKeys, remaining, new DoubleDirectComparer()); + } + return true; + } + // TODO: Specialize for string if necessary. What about the == null checks? + //else if (typeof(TKey) == typeof(string)) + //{ + // ref var specificKeys = ref Unsafe.As(ref keys); + // Sort(ref specificKeys, length, new StringDirectComparer()); + // return true; + //} + else + { + return false; + } + } + + // For sorting, move all NaN instances to front of the input array + private static int NaNPrepass( + ref TKey keys, int length, + TIsNaN isNaN) + where TIsNaN : struct, IIsNaN + { + int left = 0; + for (int i = 0; i < length; i++) + { + ref TKey current = ref Unsafe.Add(ref keys, i); + if (isNaN.IsNaN(current)) + { + // TODO: If first index is not NaN or we find just one not NaNs + // we could skip to version that no longer checks this + if (left != i) + { + ref TKey previous = ref Unsafe.Add(ref keys, left); + Swap(ref previous, ref current); + } + ++left; + } + } + return left; + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.TComparer.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.TComparer.cs new file mode 100644 index 000000000000..30c1a3f0fd3a --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.TComparer.cs @@ -0,0 +1,334 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeys_Comparer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + ref TKey keys, int length, + TComparer comparer) + where TComparer : IComparer + { + IntrospectiveSort(ref keys, length, comparer); + } + + private static void IntrospectiveSort( + ref TKey keys, int length, + TComparer comparer) + where TComparer : IComparer + { + var depthLimit = 2 * FloorLog2PlusOne(length); + IntroSort(ref keys, 0, length - 1, depthLimit, comparer); + } + + private static void IntroSort( + ref TKey keys, + int lo, int hi, int depthLimit, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + Sort2(ref keys, lo, hi, comparer); + return; + } + if (partitionSize == 3) + { + ref TKey loRef = ref Unsafe.Add(ref keys, lo); + ref TKey miRef = ref Unsafe.Add(ref keys, hi - 1); + ref TKey hiRef = ref Unsafe.Add(ref keys, hi); + //ref TKey miRef = ref Unsafe.SubtractByteOffset(ref hiRef, new IntPtr(Unsafe.SizeOf())); + Sort3(ref loRef, ref miRef, ref hiRef, comparer); + return; + } + + InsertionSort(ref keys, lo, hi, comparer); + return; + } + + if (depthLimit == 0) + { + HeapSort(ref keys, lo, hi, comparer); + return; + } + depthLimit--; + + // We should never reach here, unless > 3 elements due to partition size + int p = PickPivotAndPartition(ref keys, lo, hi, comparer); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(ref keys, p + 1, hi, depthLimit, comparer); + hi = p - 1; + } + } + + private static int PickPivotAndPartition( + ref TKey keys, int lo, int hi, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + // Compute median-of-three. But also partition them, since we've done the comparison. + + // PERF: `lo` or `hi` will never be negative inside the loop, + // so computing median using uints is safe since we know + // `length <= int.MaxValue`, and indices are >= 0 + // and thus cannot overflow an uint. + // Saves one subtraction per loop compared to + // `int middle = lo + ((hi - lo) >> 1);` + int middle = (int)(((uint)hi + (uint)lo) >> 1); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtMiddle = ref Unsafe.Add(ref keys, middle); + ref TKey keysAtHi = ref Unsafe.Add(ref keys, hi); + Sort3(ref keysAtLo, ref keysAtMiddle, ref keysAtHi, comparer); + + TKey pivot = keysAtMiddle; + + int left = lo; + int right = hi - 1; + // We already partitioned lo and hi and put the pivot in hi - 1. + // And we pre-increment & decrement below. + Swap(ref keysAtMiddle, ref Unsafe.Add(ref keys, right)); + + while (left < right) + { + // TODO: Would be good to be able to update local ref here + + while (left < (hi - 1) && comparer.Compare(Unsafe.Add(ref keys, ++left), pivot) < 0) ; + // Check if bad comparable/comparer + if (left == (hi - 1) && comparer.Compare(Unsafe.Add(ref keys, left), pivot) < 0) + ThrowHelper.ThrowArgumentException_BadComparer(comparer); + + while (right > lo && comparer.Compare(pivot, Unsafe.Add(ref keys, --right)) < 0) ; + // Check if bad comparable/comparer + if (right == lo && comparer.Compare(pivot, Unsafe.Add(ref keys, right)) < 0) + ThrowHelper.ThrowArgumentException_BadComparer(comparer); + + if (left >= right) + break; + + Swap(ref keys, left, right); + } + // Put pivot in the right location. + right = hi - 1; + if (left != right) + { + Swap(ref keys, left, right); + } + return left; + } + + private static void HeapSort( + ref TKey keys, int lo, int hi, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; --i) + { + DownHeap(ref keys, i, n, lo, comparer); + } + for (int i = n; i > 1; --i) + { + Swap(ref keys, lo, lo + i - 1); + DownHeap(ref keys, 1, i - 1, lo, comparer); + } + } + + private static void DownHeap( + ref TKey keys, int i, int n, int lo, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + + //TKey d = keys[lo + i - 1]; + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + TKey d = Unsafe.Add(ref keysAtLoMinus1, i); + var nHalf = n / 2; + while (i <= nHalf) + { + int child = i << 1; + + //if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) + if (child < n && + comparer.Compare(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child)) < 0) + { + ++child; + } + + //if (!(comparer(d, keys[lo + child - 1]) < 0)) + if (!(comparer.Compare(d, Unsafe.Add(ref keysAtLoMinus1, child)) < 0)) + break; + + // keys[lo + i - 1] = keys[lo + child - 1] + Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); + + i = child; + } + //keys[lo + i - 1] = d; + Unsafe.Add(ref keysAtLoMinus1, i) = d; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InsertionSort( + ref TKey keys, int lo, int hi, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(lo >= 0); + Debug.Assert(hi >= lo); + + for (int i = lo; i < hi; ++i) + { + int j = i; + //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); + // TODO: Would be good to be able to update local ref here + if (j >= lo && comparer.Compare(t, Unsafe.Add(ref keys, j)) < 0) + { + do + { + Unsafe.Add(ref keys, j + 1) = Unsafe.Add(ref keys, j); + --j; + } + while (j >= lo && comparer.Compare(t, Unsafe.Add(ref keys, j)) < 0); + + Unsafe.Add(ref keys, j + 1) = t; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort3( + ref TKey r0, ref TKey r1, ref TKey r2, + TComparer comparer) + where TComparer : IComparer + { + Sort2(ref r0, ref r1, comparer); + Sort2(ref r0, ref r2, comparer); + Sort2(ref r1, ref r2, comparer); + + // Below works but does not give exactly the same result as Array.Sort + // i.e. order could be a bit different for keys that are equal + //if (comparer.LessThanEqual(r0, r1)) + //{ + // // r0 <= r1 + // if (comparer.LessThanEqual(r1, r2)) + // { + // // r0 <= r1 <= r2 + // return; // Is this return good or bad for perf? + // } + // // r0 <= r1 + // // r2 < r1 + // else if (comparer.LessThanEqual(r0, r2)) + // { + // // r0 <= r2 < r1 + // Swap(ref r1, ref r2); + // } + // // r0 <= r1 + // // r2 < r1 + // // r2 < r0 + // else + // { + // // r2 < r0 <= r1 + // TKey tmp = r0; + // r0 = r2; + // r2 = r1; + // r1 = tmp; + // } + //} + //else + //{ + // // r1 < r0 + // if (comparer.LessThan(r2, r1)) + // { + // // r2 < r1 < r0 + // Swap(ref r0, ref r2); + // } + // // r1 < r0 + // // r1 <= r2 + // else if (comparer.LessThan(r2, r0)) + // { + // // r1 <= r2 < r0 + // TKey tmp = r0; + // r0 = r1; + // r1 = r2; + // r2 = tmp; + // } + // // r1 < r0 + // // r1 <= r2 + // // r0 <= r2 + // else + // { + // // r1 < r0 <= r2 + // Swap(ref r0, ref r1); + // } + //} + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey keys, int i, int j, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(i != j); + + ref TKey a = ref Unsafe.Add(ref keys, i); + ref TKey b = ref Unsafe.Add(ref keys, j); + Sort2(ref a, ref b, comparer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey a, ref TKey b, TComparer comparer) + where TComparer : IComparer + { + // This is one of the only places GreaterThan is needed + // but we need to preserve this due to bogus comparers or similar + if (comparer.Compare(a, b) > 0) + { + TKey temp = a; + a = b; + b = temp; + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.TDirectComparer.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.TDirectComparer.cs new file mode 100644 index 000000000000..54e2c376b178 --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.TDirectComparer.cs @@ -0,0 +1,328 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeys_DirectComparer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + ref TKey keys, int length, + TComparer comparer) + where TComparer : IDirectComparer + { + IntrospectiveSort(ref keys, length, comparer); + } + + private static void IntrospectiveSort( + ref TKey keys, int length, + TComparer comparer) + where TComparer : IDirectComparer + { + var depthLimit = 2 * FloorLog2PlusOne(length); + IntroSort(ref keys, 0, length - 1, depthLimit, comparer); + } + + private static void IntroSort( + ref TKey keys, + int lo, int hi, int depthLimit, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + Sort2(ref keys, lo, hi, comparer); + return; + } + if (partitionSize == 3) + { + ref TKey loRef = ref Unsafe.Add(ref keys, lo); + ref TKey miRef = ref Unsafe.Add(ref keys, hi - 1); + ref TKey hiRef = ref Unsafe.Add(ref keys, hi); + //ref TKey miRef = ref Unsafe.SubtractByteOffset(ref hiRef, new IntPtr(Unsafe.SizeOf())); + Sort3(ref loRef, ref miRef, ref hiRef, comparer); + return; + } + + InsertionSort(ref keys, lo, hi, comparer); + return; + } + + if (depthLimit == 0) + { + HeapSort(ref keys, lo, hi, comparer); + return; + } + depthLimit--; + + // We should never reach here, unless > 3 elements due to partition size + int p = PickPivotAndPartition(ref keys, lo, hi, comparer); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(ref keys, p + 1, hi, depthLimit, comparer); + hi = p - 1; + } + } + + private static int PickPivotAndPartition( + ref TKey keys, int lo, int hi, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + // Compute median-of-three. But also partition them, since we've done the comparison. + + // PERF: `lo` or `hi` will never be negative inside the loop, + // so computing median using uints is safe since we know + // `length <= int.MaxValue`, and indices are >= 0 + // and thus cannot overflow an uint. + // Saves one subtraction per loop compared to + // `int middle = lo + ((hi - lo) >> 1);` + int middle = (int)(((uint)hi + (uint)lo) >> 1); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtMiddle = ref Unsafe.Add(ref keys, middle); + ref TKey keysAtHi = ref Unsafe.Add(ref keys, hi); + Sort3(ref keysAtLo, ref keysAtMiddle, ref keysAtHi, comparer); + + TKey pivot = keysAtMiddle; + + int left = lo; + int right = hi - 1; + // We already partitioned lo and hi and put the pivot in hi - 1. + // And we pre-increment & decrement below. + Swap(ref keysAtMiddle, ref Unsafe.Add(ref keys, right)); + + while (left < right) + { + // TODO: Would be good to be able to update local ref here + + // PERF: For internal direct comparers the range checks are not needed + // since we know they cannot be bogus i.e. pass the pivot without being false. + while (comparer.LessThan(Unsafe.Add(ref keys, ++left), pivot)) ; + while (comparer.LessThan(pivot, Unsafe.Add(ref keys, --right))) ; + + if (left >= right) + break; + + Swap(ref keys, left, right); + } + // Put pivot in the right location. + right = hi - 1; + if (left != right) + { + Swap(ref keys, left, right); + } + return left; + } + + private static void HeapSort( + ref TKey keys, int lo, int hi, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; --i) + { + DownHeap(ref keys, i, n, lo, comparer); + } + for (int i = n; i > 1; --i) + { + Swap(ref keys, lo, lo + i - 1); + DownHeap(ref keys, 1, i - 1, lo, comparer); + } + } + + private static void DownHeap( + ref TKey keys, int i, int n, int lo, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + + //TKey d = keys[lo + i - 1]; + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + TKey d = Unsafe.Add(ref keysAtLoMinus1, i); + var nHalf = n / 2; + while (i <= nHalf) + { + int child = i << 1; + + //if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) + if (child < n && + comparer.LessThan(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child))) + { + ++child; + } + + //if (!(comparer(d, keys[lo + child - 1]) < 0)) + if (!(comparer.LessThan(d, Unsafe.Add(ref keysAtLoMinus1, child)))) + break; + + // keys[lo + i - 1] = keys[lo + child - 1] + Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); + + i = child; + } + //keys[lo + i - 1] = d; + Unsafe.Add(ref keysAtLoMinus1, i) = d; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InsertionSort( + ref TKey keys, int lo, int hi, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(lo >= 0); + Debug.Assert(hi >= lo); + + for (int i = lo; i < hi; ++i) + { + int j = i; + //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); + // TODO: Would be good to be able to update local ref here + if (j >= lo && comparer.LessThan(t, Unsafe.Add(ref keys, j))) + { + do + { + Unsafe.Add(ref keys, j + 1) = Unsafe.Add(ref keys, j); + --j; + } + while (j >= lo && comparer.LessThan(t, Unsafe.Add(ref keys, j))); + + Unsafe.Add(ref keys, j + 1) = t; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort3( + ref TKey r0, ref TKey r1, ref TKey r2, + TComparer comparer) + where TComparer : IDirectComparer + { + Sort2(ref r0, ref r1, comparer); + Sort2(ref r0, ref r2, comparer); + Sort2(ref r1, ref r2, comparer); + + // Below works but does not give exactly the same result as Array.Sort + // i.e. order could be a bit different for keys that are equal + //if (comparer.LessThanEqual(r0, r1)) + //{ + // // r0 <= r1 + // if (comparer.LessThanEqual(r1, r2)) + // { + // // r0 <= r1 <= r2 + // return; // Is this return good or bad for perf? + // } + // // r0 <= r1 + // // r2 < r1 + // else if (comparer.LessThanEqual(r0, r2)) + // { + // // r0 <= r2 < r1 + // Swap(ref r1, ref r2); + // } + // // r0 <= r1 + // // r2 < r1 + // // r2 < r0 + // else + // { + // // r2 < r0 <= r1 + // TKey tmp = r0; + // r0 = r2; + // r2 = r1; + // r1 = tmp; + // } + //} + //else + //{ + // // r1 < r0 + // if (comparer.LessThan(r2, r1)) + // { + // // r2 < r1 < r0 + // Swap(ref r0, ref r2); + // } + // // r1 < r0 + // // r1 <= r2 + // else if (comparer.LessThan(r2, r0)) + // { + // // r1 <= r2 < r0 + // TKey tmp = r0; + // r0 = r1; + // r1 = r2; + // r2 = tmp; + // } + // // r1 < r0 + // // r1 <= r2 + // // r0 <= r2 + // else + // { + // // r1 < r0 <= r2 + // Swap(ref r0, ref r1); + // } + //} + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey keys, int i, int j, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(i != j); + + ref TKey a = ref Unsafe.Add(ref keys, i); + ref TKey b = ref Unsafe.Add(ref keys, j); + Sort2(ref a, ref b, comparer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey a, ref TKey b, TComparer comparer) + where TComparer : IDirectComparer + { + // This is one of the only places GreaterThan is needed + // but we need to preserve this due to bogus comparers or similar + if (comparer.GreaterThan(a, b)) + { + TKey temp = a; + a = b; + b = temp; + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.cs new file mode 100644 index 000000000000..7a68cb0b6529 --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.cs @@ -0,0 +1,234 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; +using S = System.SpanSortHelpersKeys; +using SC = System.SpanSortHelpersKeys_Comparer; +using SDC = System.SpanSortHelpersKeys_DirectComparer; + +namespace System +{ + internal static partial class SpanSortHelpersKeys + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort(this Span keys) + { + int length = keys.Length; + if (length < 2) + return; + + // PERF: Try specialized here for optimal performance + // Code-gen is weird unless used in loop outside + if (!SDC.TrySortSpecialized( + ref MemoryMarshal.GetReference(keys), + length)) + { + DefaultSpanSortHelper.s_default.Sort( + ref MemoryMarshal.GetReference(keys), + length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + this Span keys, TComparer comparer) + where TComparer : IComparer + { + int length = keys.Length; + if (length < 2) + return; + + DefaultSpanSortHelper.s_default.Sort( + ref MemoryMarshal.GetReference(keys), + length, comparer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + this Span keys, Comparison comparison) + { + int length = keys.Length; + if (length < 2) + return; + + DefaultSpanSortHelper.s_default.Sort( + ref MemoryMarshal.GetReference(keys), + length, comparison); + } + + internal static class DefaultSpanSortHelper + { + internal static readonly ISpanSortHelper s_default = CreateSortHelper(); + + private static ISpanSortHelper CreateSortHelper() + { + if (typeof(IComparable).IsAssignableFrom(typeof(TKey))) + { + // coreclr uses RuntimeTypeHandle.Allocate + var ctor = typeof(ComparableSpanSortHelper<>) + .MakeGenericType(new Type[] { typeof(TKey) }) + .GetConstructor(Array.Empty()); + + return (ISpanSortHelper)ctor.Invoke(Array.Empty()); + } + else + { + return new SpanSortHelper(); + } + } + } + + internal interface ISpanSortHelper + { + void Sort(ref TKey keys, int length); + void Sort(ref TKey keys, int length, Comparison comparison); + } + + internal class SpanSortHelper : ISpanSortHelper + { + public void Sort(ref TKey keys, int length) + { + SC.Sort(ref keys, length, Comparer.Default); + } + + public void Sort(ref TKey keys, int length, Comparison comparison) + { + S.Sort(ref keys, length, comparison); + } + } + + internal class ComparableSpanSortHelper + : ISpanSortHelper + where TKey : IComparable + { + public void Sort(ref TKey keys, int length) + { + S.Sort(ref keys, length); + } + + public void Sort(ref TKey keys, int length, Comparison comparison) + { + // TODO: Check if comparison is Comparer.Default.Compare + + S.Sort(ref keys, length, comparison); + } + } + + + internal static class DefaultSpanSortHelper + where TComparer : IComparer + { + internal static readonly ISpanSortHelper s_default = CreateSortHelper(); + + private static ISpanSortHelper CreateSortHelper() + { + if (typeof(IComparable).IsAssignableFrom(typeof(TKey))) + { + // coreclr uses RuntimeTypeHandle.Allocate + var ctor = typeof(ComparableSpanSortHelper<,>) + .MakeGenericType(new Type[] { typeof(TKey), typeof(TComparer) }) + .GetConstructor(Array.Empty()); + + return (ISpanSortHelper)ctor.Invoke(Array.Empty()); + } + else + { + return new SpanSortHelper(); + } + } + } + + // https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Collections/Generic/ArraySortHelper.cs + internal interface ISpanSortHelper + where TComparer : IComparer + { + void Sort(ref TKey keys, int length, TComparer comparer); + } + + internal class SpanSortHelper : ISpanSortHelper + where TComparer : IComparer + { + public void Sort(ref TKey keys, int length, TComparer comparer) + { + // Add a try block here to detect IComparers (or their + // underlying IComparables, etc) that are bogus. + // + // TODO: Do we need the try/catch? + //try + //{ + if (typeof(TComparer) == typeof(IComparer) && comparer == null) + { + SC.Sort(ref keys, length, Comparer.Default); + } + else + { + SC.Sort(ref keys, length, comparer); + } + //} + //catch (IndexOutOfRangeException e) + //{ + // throw e; + // //IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); + //} + //catch (Exception e) + //{ + // throw e; + // //throw new InvalidOperationException(SR.InvalidOperation_IComparerFailed, e); + //} + } + } + + internal class ComparableSpanSortHelper + : ISpanSortHelper + where TKey : IComparable + where TComparer : IComparer + { + public void Sort(ref TKey keys, int length, + TComparer comparer) + { + // Add a try block here to detect IComparers (or their + // underlying IComparables, etc) that are bogus. + // + // TODO: Do we need the try/catch? + //try + //{ + if (comparer == null || + // Cache this in generic traits helper class perhaps + (!typeof(TComparer).IsValueType && + object.ReferenceEquals(comparer, Comparer.Default))) // Or "=="? + { + if (!SDC.TrySortSpecialized(ref keys, length)) + { + // NOTE: For Bogus Comparable the exception message will be different, when using Comparer.Default + // Since the exception message is thrown internally without knowledge of the comparer + S.Sort(ref keys, length); + } + } + else + { + SC.Sort(ref keys, length, comparer); + } + //} + //catch (IndexOutOfRangeException e) + //{ + // throw e; + // //IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); + //} + //catch (Exception e) + //{ + // throw e; + // //throw new InvalidOperationException(SR.InvalidOperation_IComparerFailed, e); + //} + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Comparison.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Comparison.cs new file mode 100644 index 000000000000..efc8c50b381f --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Comparison.cs @@ -0,0 +1,284 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeysValues + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + ref TKey keys, ref TValue values, int length, + Comparison comparison) + { + IntrospectiveSort(ref keys, ref values, length, comparison); + } + + private static void IntrospectiveSort( + ref TKey keys, ref TValue values, int length, + Comparison comparison) + { + var depthLimit = 2 * FloorLog2PlusOne(length); + IntroSort(ref keys, ref values, 0, length - 1, depthLimit, comparison); + } + + private static void IntroSort( + ref TKey keys, ref TValue values, + int lo, int hi, int depthLimit, + Comparison comparison) + { + Debug.Assert(comparison != null); + Debug.Assert(lo >= 0); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + Sort2(ref keys, ref values, lo, hi, comparison); + return; + } + if (partitionSize == 3) + { + Sort3(ref keys, ref values, lo, hi - 1, hi, comparison); + return; + } + InsertionSort(ref keys, ref values, lo, hi, comparison); + return; + } + + if (depthLimit == 0) + { + HeapSort(ref keys, ref values, lo, hi, comparison); + return; + } + depthLimit--; + + // We should never reach here, unless > 3 elements due to partition size + int p = PickPivotAndPartition(ref keys, ref values, lo, hi, comparison); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(ref keys, ref values, p + 1, hi, depthLimit, comparison); + hi = p - 1; + } + } + + private static int PickPivotAndPartition( + ref TKey keys, ref TValue values, int lo, int hi, + Comparison comparison) + + { + Debug.Assert(comparison != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + // Compute median-of-three. But also partition them, since we've done the comparison. + + // PERF: `lo` or `hi` will never be negative inside the loop, + // so computing median using uints is safe since we know + // `length <= int.MaxValue`, and indices are >= 0 + // and thus cannot overflow an uint. + // Saves one subtraction per loop compared to + // `int middle = lo + ((hi - lo) >> 1);` + int middle = (int)(((uint)hi + (uint)lo) >> 1); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + ref TKey keysAtMiddle = ref Sort3(ref keys, ref values, lo, middle, hi, comparison); + + TKey pivot = keysAtMiddle; + + int left = lo; + int right = hi - 1; + // We already partitioned lo and hi and put the pivot in hi - 1. + // And we pre-increment & decrement below. + Swap(ref keysAtMiddle, ref Unsafe.Add(ref keys, right)); + Swap(ref values, middle, right); + + while (left < right) + { + // TODO: Would be good to be able to update local ref here + + // TODO: For primitives and internal comparers the range checks can be eliminated + + while (left < (hi - 1) && comparison(Unsafe.Add(ref keys, ++left), pivot) < 0) ; + // Check if bad comparable/comparison + if (left == (hi - 1) && comparison(Unsafe.Add(ref keys, left), pivot) < 0) + ThrowHelper.ThrowArgumentException_BadComparer(comparison); + + while (right > lo && comparison(pivot, Unsafe.Add(ref keys, --right)) < 0) ; + // Check if bad comparable/comparison + if (right == lo && comparison(pivot, Unsafe.Add(ref keys, right)) < 0) + ThrowHelper.ThrowArgumentException_BadComparer(comparison); + + if (left >= right) + break; + + Swap(ref keys, left, right); + Swap(ref values, left, right); + } + // Put pivot in the right location. + right = hi - 1; + if (left != right) + { + Swap(ref keys, left, right); + Swap(ref values, left, right); + } + return left; + } + + private static void HeapSort( + ref TKey keys, ref TValue values, int lo, int hi, + Comparison comparison) + + { + Debug.Assert(comparison != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; --i) + { + DownHeap(ref keys, ref values, i, n, lo, comparison); + } + for (int i = n; i > 1; --i) + { + Swap(ref keys, lo, lo + i - 1); + Swap(ref values, lo, lo + i - 1); + DownHeap(ref keys, ref values, 1, i - 1, lo, comparison); + } + } + + private static void DownHeap( + ref TKey keys, ref TValue values, int i, int n, int lo, + Comparison comparison) + + { + Debug.Assert(comparison != null); + Debug.Assert(lo >= 0); + + //TKey d = keys[lo + i - 1]; + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + + ref TValue valuesAtLoMinus1 = ref Unsafe.Add(ref values, lo - 1); + + TKey d = Unsafe.Add(ref keysAtLoMinus1, i); + TValue dValue = Unsafe.Add(ref valuesAtLoMinus1, i); + + var nHalf = n / 2; + while (i <= nHalf) + { + int child = i << 1; + + //if (child < n && comparison(keys[lo + child - 1], keys[lo + child]) < 0) + if (child < n && + comparison(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child)) < 0) + { + ++child; + } + + //if (!(comparison(d, keys[lo + child - 1]) < 0)) + if (!(comparison(d, Unsafe.Add(ref keysAtLoMinus1, child)) < 0)) + break; + + // keys[lo + i - 1] = keys[lo + child - 1] + Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); + Unsafe.Add(ref valuesAtLoMinus1, i) = Unsafe.Add(ref valuesAtLoMinus1, child); + + i = child; + } + //keys[lo + i - 1] = d; + Unsafe.Add(ref keysAtLoMinus1, i) = d; + Unsafe.Add(ref valuesAtLoMinus1, i) = dValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InsertionSort( + ref TKey keys, ref TValue values, int lo, int hi, + Comparison comparison) + + { + Debug.Assert(lo >= 0); + Debug.Assert(hi >= lo); + + for (int i = lo; i < hi; ++i) + { + int j = i; + //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); + // TODO: Would be good to be able to update local ref here + if (j >= lo && comparison(t, Unsafe.Add(ref keys, j)) < 0) + { + var v = Unsafe.Add(ref values, j + 1); + do + { + Unsafe.Add(ref keys, j + 1) = Unsafe.Add(ref keys, j); + Unsafe.Add(ref values, j + 1) = Unsafe.Add(ref values, j); + --j; + } + while (j >= lo && comparison(t, Unsafe.Add(ref keys, j)) < 0); + + Unsafe.Add(ref keys, j + 1) = t; + Unsafe.Add(ref values, j + 1) = v; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ref TKey Sort3( + ref TKey keys, ref TValue values, int i0, int i1, int i2, + Comparison comparison) + + { + ref var r0 = ref Unsafe.Add(ref keys, i0); + ref var r1 = ref Unsafe.Add(ref keys, i1); + ref var r2 = ref Unsafe.Add(ref keys, i2); + Sort2(ref r0, ref r1, comparison, ref values, i0, i1); + Sort2(ref r0, ref r2, comparison, ref values, i0, i2); + Sort2(ref r1, ref r2, comparison, ref values, i1, i2); + return ref r1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey keys, ref TValue values, int i, int j, + Comparison comparison) + + { + Debug.Assert(i != j); + + ref TKey a = ref Unsafe.Add(ref keys, i); + ref TKey b = ref Unsafe.Add(ref keys, j); + Sort2(ref a, ref b, comparison, ref values, i, j); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey a, ref TKey b, Comparison comparison, + ref TValue values, int i, int j) + + { + // This is one of the only places GreaterThan is needed + // but we need to preserve this due to bogus comparers or similar + if (comparison(a, b) > 0) + { + Swap(ref a, ref b); + Swap(ref values, i, j); + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.IComparable.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.IComparable.cs new file mode 100644 index 000000000000..ba3b68c3bd07 --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.IComparable.cs @@ -0,0 +1,347 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeysValues + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + ref TKey keys, ref TValue values, int length) + where TKey : IComparable + { + IntrospectiveSort(ref keys, ref values, length); + } + + private static void IntrospectiveSort( + ref TKey keys, ref TValue values, int length) + where TKey : IComparable + { + var depthLimit = 2 * FloorLog2PlusOne(length); + IntroSort(ref keys, ref values, 0, length - 1, depthLimit); + } + + private static void IntroSort( + ref TKey keys, ref TValue values, + int lo, int hi, int depthLimit) + where TKey : IComparable + { + Debug.Assert(lo >= 0); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + Sort2(ref keys, ref values, lo, hi); + return; + } + if (partitionSize == 3) + { + Sort3(ref keys, ref values, lo, hi - 1, hi); + return; + } + InsertionSort(ref keys, ref values, lo, hi); + return; + } + + if (depthLimit == 0) + { + HeapSort(ref keys, ref values, lo, hi); + return; + } + depthLimit--; + + // We should never reach here, unless > 3 elements due to partition size + int p = PickPivotAndPartition(ref keys, ref values, lo, hi); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(ref keys, ref values, p + 1, hi, depthLimit); + hi = p - 1; + } + } + + private static int PickPivotAndPartition( + ref TKey keys, ref TValue values, int lo, int hi) + where TKey : IComparable + { + + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + // Compute median-of-three. But also partition them, since we've done the comparison. + + // PERF: `lo` or `hi` will never be negative inside the loop, + // so computing median using uints is safe since we know + // `length <= int.MaxValue`, and indices are >= 0 + // and thus cannot overflow an uint. + // Saves one subtraction per loop compared to + // `int middle = lo + ((hi - lo) >> 1);` + int middle = (int)(((uint)hi + (uint)lo) >> 1); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + ref TKey keysAtMiddle = ref Sort3(ref keys, ref values, lo, middle, hi); + + TKey pivot = keysAtMiddle; + + int left = lo; + int right = hi - 1; + // We already partitioned lo and hi and put the pivot in hi - 1. + // And we pre-increment & decrement below. + Swap(ref keysAtMiddle, ref Unsafe.Add(ref keys, right)); + Swap(ref values, middle, right); + + while (left < right) + { + // TODO: Would be good to be able to update local ref here + + if (pivot == null) + { + while (left < (hi - 1) && Unsafe.Add(ref keys, ++left) == null) ; + while (right > lo && Unsafe.Add(ref keys, --right) != null) ; + } + else + { + while (left < (hi - 1) && pivot.CompareTo(Unsafe.Add(ref keys, ++left)) > 0) ; + // Check if bad comparable/comparer + if (left == (hi - 1) && pivot.CompareTo(Unsafe.Add(ref keys, left)) > 0) + ThrowHelper.ThrowArgumentException_BadComparable(typeof(TKey)); + + while (right > lo && pivot.CompareTo(Unsafe.Add(ref keys, --right)) < 0) ; + // Check if bad comparable/comparer + if (right == lo && pivot.CompareTo(Unsafe.Add(ref keys, right)) < 0) + ThrowHelper.ThrowArgumentException_BadComparable(typeof(TKey)); + } + + if (left >= right) + break; + + Swap(ref keys, left, right); + Swap(ref values, left, right); + } + // Put pivot in the right location. + right = hi - 1; + if (left != right) + { + Swap(ref keys, left, right); + Swap(ref values, left, right); + } + return left; + } + + private static void HeapSort( + ref TKey keys, ref TValue values, int lo, int hi + ) + where TKey : IComparable + { + + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; --i) + { + DownHeap(ref keys, ref values, i, n, lo); + } + for (int i = n; i > 1; --i) + { + Swap(ref keys, lo, lo + i - 1); + Swap(ref values, lo, lo + i - 1); + DownHeap(ref keys, ref values, 1, i - 1, lo); + } + } + + private static void DownHeap( + ref TKey keys, ref TValue values, int i, int n, int lo) + where TKey : IComparable + { + + Debug.Assert(lo >= 0); + + //TKey d = keys[lo + i - 1]; + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + + ref TValue valuesAtLoMinus1 = ref Unsafe.Add(ref values, lo - 1); + + TKey d = Unsafe.Add(ref keysAtLoMinus1, i); + TValue dValue = Unsafe.Add(ref valuesAtLoMinus1, i); + + var nHalf = n / 2; + while (i <= nHalf) + { + int child = i << 1; + + //if (child < n && (keys[lo + child - 1] == null || keys[lo + child - 1].CompareTo(keys[lo + child]) < 0)) + if (child < n && + (Unsafe.Add(ref keysAtLoMinus1, child) == null || + Unsafe.Add(ref keysAtLoMinus1, child).CompareTo(Unsafe.Add(ref keysAtLo, child)) < 0)) + { + ++child; + } + + //if (keys[lo + child - 1] == null || keys[lo + child - 1].CompareTo(d) < 0) + if (Unsafe.Add(ref keysAtLoMinus1, child) == null || + Unsafe.Add(ref keysAtLoMinus1, child).CompareTo(d) < 0) + break; + + // keys[lo + i - 1] = keys[lo + child - 1] + Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); + Unsafe.Add(ref valuesAtLoMinus1, i) = Unsafe.Add(ref valuesAtLoMinus1, child); + + i = child; + } + //keys[lo + i - 1] = d; + Unsafe.Add(ref keysAtLoMinus1, i) = d; + Unsafe.Add(ref valuesAtLoMinus1, i) = dValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InsertionSort( + ref TKey keys, ref TValue values, int lo, int hi) + where TKey : IComparable + { + Debug.Assert(lo >= 0); + Debug.Assert(hi >= lo); + + for (int i = lo; i < hi; ++i) + { + int j = i; + //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); + // TODO: Would be good to be able to update local ref here + if (j >= lo && (t == null || t.CompareTo(Unsafe.Add(ref keys, j)) < 0)) + { + var v = Unsafe.Add(ref values, j + 1); + do + { + Unsafe.Add(ref keys, j + 1) = Unsafe.Add(ref keys, j); + Unsafe.Add(ref values, j + 1) = Unsafe.Add(ref values, j); + --j; + } + while (j >= lo && (t == null || t.CompareTo(Unsafe.Add(ref keys, j)) < 0)); + //while (j >= lo && (t == null || t.CompareTo(keys[j]) < 0)) + + Unsafe.Add(ref keys, j + 1) = t; + Unsafe.Add(ref values, j + 1) = v; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ref TKey Sort3( + ref TKey keys, ref TValue values, int i0, int i1, int i2) + where TKey : IComparable + { + ref var r0 = ref Unsafe.Add(ref keys, i0); + ref var r1 = ref Unsafe.Add(ref keys, i1); + ref var r2 = ref Unsafe.Add(ref keys, i2); + Sort2(ref r0, ref r1, ref values, i0, i1); + Sort2(ref r0, ref r2, ref values, i0, i2); + Sort2(ref r1, ref r2, ref values, i1, i2); + + //ref var r0 = ref Unsafe.Add(ref keys, i0); + //ref var r1 = ref Unsafe.Add(ref keys, i1); + //ref var r2 = ref Unsafe.Add(ref keys, i2); + + //if (r0 != null && r0.CompareTo(r1) <= 0) //r0 <= r1) + //{ + // if (r1 != null && r1.CompareTo(r2) <= 0) //(r1 <= r2) + // { + // return ref r1; + // } + // else if (r0.CompareTo(r2) < 0) //(r0 < r2) + // { + // Swap(ref r1, ref r2); + // ref var v1 = ref Unsafe.Add(ref values, i1); + // ref var v2 = ref Unsafe.Add(ref values, i2); + // Swap(ref v1, ref v2); + // } + // else + // { + // TKey tmp = r0; + // r0 = r2; + // r2 = r1; + // r1 = tmp; + // ref var v0 = ref Unsafe.Add(ref values, i0); + // ref var v1 = ref Unsafe.Add(ref values, i1); + // ref var v2 = ref Unsafe.Add(ref values, i2); + // TValue vTemp = v0; + // v0 = v2; + // v2 = v1; + // v1 = vTemp; + // } + //} + //else + //{ + // if (r0 != null && r0.CompareTo(r2) < 0) //(r0 < r2) + // { + // Swap(ref r0, ref r1); + // ref var v0 = ref Unsafe.Add(ref values, i0); + // ref var v1 = ref Unsafe.Add(ref values, i1); + // Swap(ref v0, ref v1); + // } + // else if (r2 != null && r2.CompareTo(r1) < 0) //(r2 < r1) + // { + // Swap(ref r0, ref r2); + // ref var v0 = ref Unsafe.Add(ref values, i0); + // ref var v2 = ref Unsafe.Add(ref values, i2); + // Swap(ref v0, ref v2); + // } + // else + // { + // TKey tmp = r0; + // r0 = r1; + // r1 = r2; + // r2 = tmp; + // ref var v0 = ref Unsafe.Add(ref values, i0); + // ref var v1 = ref Unsafe.Add(ref values, i1); + // ref var v2 = ref Unsafe.Add(ref values, i2); + // TValue vTemp = v0; + // v0 = v1; + // v1 = v2; + // v2 = vTemp; + // } + //} + return ref r1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey keys, ref TValue values, int i, int j) + where TKey : IComparable + { + Debug.Assert(i != j); + + ref TKey a = ref Unsafe.Add(ref keys, i); + ref TKey b = ref Unsafe.Add(ref keys, j); + Sort2(ref a, ref b, ref values, i, j); + } + + private static void Sort2( + ref TKey a, ref TKey b, ref TValue values, int i, int j) + where TKey : IComparable + { + if (a != null && a.CompareTo(b) > 0) + { + Swap(ref a, ref b); + Swap(ref values, i, j); + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Specialized.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Specialized.cs new file mode 100644 index 000000000000..0f3ec6348a47 --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Specialized.cs @@ -0,0 +1,175 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeysValues_DirectComparer + { + // https://github.com/dotnet/coreclr/blob/master/src/classlibnative/bcltype/arrayhelpers.cpp + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TrySortSpecialized( + ref TKey keys, ref TValue values, int length) + { + // TODO: Change to do specific value tests below, since otherwise not fast... maybe + if (typeof(TKey) != typeof(TValue)) + { + return false; + } + else + // Types unfolding adopted from https://github.com/dotnet/coreclr/blob/master/src/classlibnative/bcltype/arrayhelpers.cpp#L268 + if (typeof(TKey) == typeof(sbyte)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, ref values, length, new SByteDirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(byte) || + typeof(TKey) == typeof(bool)) // Use byte for bools to reduce code size + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, ref values, length, new ByteDirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(short)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, ref values, length, new Int16DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(ushort) || + typeof(TKey) == typeof(char)) // Use ushort for chars to reduce code size) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, ref values, length, new UInt16DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(int)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, ref values, length, new Int32DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(uint)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, ref values, length, new UInt32DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(long)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, ref values, length, new Int64DirectComparer()); + return true; + } + else if (typeof(TKey) == typeof(ulong)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + Sort(ref specificKeys, ref values, length, new UInt64DirectComparer()); + return true; + } + // Array.Sort only uses NaNPrepass when both key and value are the same, + // to give exactly the same result we have to do the same. + // Not only that, the comparisons will then be different since when using normal sort + // code the IComparable<> path is different than the specialized. + else if (typeof(TKey) == typeof(float) && typeof(TValue) == typeof(float)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + // Array.Sort only uses NaNPrepass when both key and value are the same + //if (typeof(TValue) == typeof(float)) + { + // Comparison to NaN is always false, so do a linear pass + // and swap all NaNs to the front of the array + var left = NaNPrepass(ref specificKeys, ref values, length, new SingleIsNaN()); + + var remaining = length - left; + if (remaining > 1) + { + ref var afterNaNsKeys = ref Unsafe.Add(ref specificKeys, left); + ref var afterNaNsValues = ref Unsafe.Add(ref values, left); + Sort(ref afterNaNsKeys, ref afterNaNsValues, remaining, new SingleDirectComparer()); + } + } + //else + //{ + // Sort(ref specificKeys, ref values, length, new SingleDirectComparer()); + //} + return true; + } + // Array.Sort only uses NaNPrepass when both key and value are the same, + // to give exactly the same result we have to do the same. + // Not only that, the comparisons will then be different since when using normal sort + // code the IComparable<> path is different than the specialized. + else if (typeof(TKey) == typeof(double) && typeof(TValue) == typeof(double)) + { + ref var specificKeys = ref Unsafe.As(ref keys); + // Array.Sort only uses NaNPrepass when both key and value are the same + //if (typeof(TValue) == typeof(double)) + { + // Comparison to NaN is always false, so do a linear pass + // and swap all NaNs to the front of the array + var left = NaNPrepass(ref specificKeys, ref values, length, new DoubleIsNaN()); + + var remaining = length - left; + if (remaining > 1) + { + ref var afterNaNsKeys = ref Unsafe.Add(ref specificKeys, left); + ref var afterNaNsValues = ref Unsafe.Add(ref values, left); + Sort(ref afterNaNsKeys, ref afterNaNsValues, remaining, new DoubleDirectComparer()); + } + } + //else + //{ + // Sort(ref specificKeys, ref values, length, new DoubleDirectComparer()); + //} + return true; + } + // TODO: Specialize for string if necessary. What about the == null checks? + //else if (typeof(TKey) == typeof(string)) + //{ + // ref var specificKeys = ref Unsafe.As(ref keys); + // Sort(ref specificKeys, ref values, length, new StringDirectComparer()); + // return true; + //} + else + { + return false; + } + } + + // For sorting, move all NaN instances to front of the input array + private static int NaNPrepass( + ref TKey keys, ref TValue values, int length, + TIsNaN isNaN) + where TIsNaN : struct, IIsNaN + { + int left = 0; + for (int i = 0; i < length; i++) + { + ref TKey current = ref Unsafe.Add(ref keys, i); + if (isNaN.IsNaN(current)) + { + // TODO: If first index is not NaN or we find just one not NaNs + // we could skip to version that no longer checks this + if (left != i) + { + ref TKey previous = ref Unsafe.Add(ref keys, left); + Swap(ref previous, ref current); + Swap(ref values, left, i); + } + ++left; + } + } + return left; + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TComparer.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TComparer.cs new file mode 100644 index 000000000000..89c1f5d3117f --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TComparer.cs @@ -0,0 +1,288 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeysValues + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + ref TKey keys, ref TValue values, int length, + TComparer comparer) + where TComparer : IComparer + { + IntrospectiveSort(ref keys, ref values, length, comparer); + } + + private static void IntrospectiveSort( + ref TKey keys, ref TValue values, int length, + TComparer comparer) + where TComparer : IComparer + { + var depthLimit = 2 * FloorLog2PlusOne(length); + IntroSort(ref keys, ref values, 0, length - 1, depthLimit, comparer); + } + + private static void IntroSort( + ref TKey keys, ref TValue values, + int lo, int hi, int depthLimit, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + Sort2(ref keys, ref values, lo, hi, comparer); + return; + } + if (partitionSize == 3) + { + Sort3(ref keys, ref values, lo, hi - 1, hi, comparer); + return; + } + InsertionSort(ref keys, ref values, lo, hi, comparer); + return; + } + + if (depthLimit == 0) + { + HeapSort(ref keys, ref values, lo, hi, comparer); + return; + } + depthLimit--; + + // We should never reach here, unless > 3 elements due to partition size + int p = PickPivotAndPartition(ref keys, ref values, lo, hi, comparer); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(ref keys, ref values, p + 1, hi, depthLimit, comparer); + hi = p - 1; + } + } + + private static int PickPivotAndPartition( + ref TKey keys, ref TValue values, int lo, int hi, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + // Compute median-of-three. But also partition them, since we've done the comparison. + + // PERF: `lo` or `hi` will never be negative inside the loop, + // so computing median using uints is safe since we know + // `length <= int.MaxValue`, and indices are >= 0 + // and thus cannot overflow an uint. + // Saves one subtraction per loop compared to + // `int middle = lo + ((hi - lo) >> 1);` + int middle = (int)(((uint)hi + (uint)lo) >> 1); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + ref TKey keysAtMiddle = ref Sort3(ref keys, ref values, lo, middle, hi, comparer); + + TKey pivot = keysAtMiddle; + + int left = lo; + int right = hi - 1; + // We already partitioned lo and hi and put the pivot in hi - 1. + // And we pre-increment & decrement below. + Swap(ref keysAtMiddle, ref Unsafe.Add(ref keys, right)); + Swap(ref values, middle, right); + + while (left < right) + { + // TODO: Would be good to be able to update local ref here + + // TODO: For primitives and internal comparers the range checks can be eliminated + + while (left < (hi - 1) && comparer.Compare(Unsafe.Add(ref keys, ++left), pivot) < 0) ; + // Check if bad comparable/comparer + if (left == (hi - 1) && comparer.Compare(Unsafe.Add(ref keys, left), pivot) < 0) + ThrowHelper.ThrowArgumentException_BadComparer(comparer); + + while (right > lo && comparer.Compare(pivot, Unsafe.Add(ref keys, --right)) < 0) ; + // Check if bad comparable/comparer + if (right == lo && comparer.Compare(pivot, Unsafe.Add(ref keys, right)) < 0) + ThrowHelper.ThrowArgumentException_BadComparer(comparer); + + if (left >= right) + break; + + Swap(ref keys, left, right); + Swap(ref values, left, right); + } + // Put pivot in the right location. + right = hi - 1; + if (left != right) + { + Swap(ref keys, left, right); + Swap(ref values, left, right); + } + return left; + } + + private static void HeapSort( + ref TKey keys, ref TValue values, int lo, int hi, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; --i) + { + DownHeap(ref keys, ref values, i, n, lo, comparer); + } + for (int i = n; i > 1; --i) + { + Swap(ref keys, lo, lo + i - 1); + Swap(ref values, lo, lo + i - 1); + DownHeap(ref keys, ref values, 1, i - 1, lo, comparer); + } + } + + private static void DownHeap( + ref TKey keys, ref TValue values, int i, int n, int lo, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + + //TKey d = keys[lo + i - 1]; + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + + ref TValue valuesAtLoMinus1 = ref Unsafe.Add(ref values, lo - 1); + + TKey d = Unsafe.Add(ref keysAtLoMinus1, i); + TValue dValue = Unsafe.Add(ref valuesAtLoMinus1, i); + + var nHalf = n / 2; + while (i <= nHalf) + { + int child = i << 1; + + //if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) + if (child < n && + comparer.Compare(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child)) < 0) + { + ++child; + } + + //if (!(comparer(d, keys[lo + child - 1]) < 0)) + if (!(comparer.Compare(d, Unsafe.Add(ref keysAtLoMinus1, child)) < 0)) + break; + + // keys[lo + i - 1] = keys[lo + child - 1] + Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); + Unsafe.Add(ref valuesAtLoMinus1, i) = Unsafe.Add(ref valuesAtLoMinus1, child); + + i = child; + } + //keys[lo + i - 1] = d; + Unsafe.Add(ref keysAtLoMinus1, i) = d; + Unsafe.Add(ref valuesAtLoMinus1, i) = dValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InsertionSort( + ref TKey keys, ref TValue values, int lo, int hi, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(lo >= 0); + Debug.Assert(hi >= lo); + + for (int i = lo; i < hi; ++i) + { + int j = i; + //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); + // TODO: Would be good to be able to update local ref here + if (j >= lo && comparer.Compare(t, Unsafe.Add(ref keys, j)) < 0) + { + var v = Unsafe.Add(ref values, j + 1); + do + { + Unsafe.Add(ref keys, j + 1) = Unsafe.Add(ref keys, j); + Unsafe.Add(ref values, j + 1) = Unsafe.Add(ref values, j); + --j; + } + while (j >= lo && comparer.Compare(t, Unsafe.Add(ref keys, j)) < 0); + + Unsafe.Add(ref keys, j + 1) = t; + Unsafe.Add(ref values, j + 1) = v; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ref TKey Sort3( + ref TKey keys, ref TValue values, int i0, int i1, int i2, + TComparer comparer) + where TComparer : IComparer + { + ref var r0 = ref Unsafe.Add(ref keys, i0); + ref var r1 = ref Unsafe.Add(ref keys, i1); + ref var r2 = ref Unsafe.Add(ref keys, i2); + Sort2(ref r0, ref r1, comparer, ref values, i0, i1); + Sort2(ref r0, ref r2, comparer, ref values, i0, i2); + Sort2(ref r1, ref r2, comparer, ref values, i1, i2); + return ref r1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey keys, ref TValue values, int i, int j, + TComparer comparer) + where TComparer : IComparer + { + Debug.Assert(i != j); + + ref TKey a = ref Unsafe.Add(ref keys, i); + ref TKey b = ref Unsafe.Add(ref keys, j); + Sort2(ref a, ref b, comparer, ref values, i, j); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey a, ref TKey b, TComparer comparer, + ref TValue values, int i, int j) + where TComparer : IComparer + { + // This is one of the only places GreaterThan is needed + // but we need to preserve this due to bogus comparers or similar + if (comparer.Compare(a, b) > 0) + { + Swap(ref a, ref b); + Swap(ref values, i, j); + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TDirectComparer.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TDirectComparer.cs new file mode 100644 index 000000000000..971e5c689fe9 --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TDirectComparer.cs @@ -0,0 +1,281 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; + +namespace System +{ + internal static partial class SpanSortHelpersKeysValues_DirectComparer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + ref TKey keys, ref TValue values, int length, + TComparer comparer) + where TComparer : IDirectComparer + { + IntrospectiveSort(ref keys, ref values, length, comparer); + } + + private static void IntrospectiveSort( + ref TKey keys, ref TValue values, int length, + TComparer comparer) + where TComparer : IDirectComparer + { + var depthLimit = 2 * FloorLog2PlusOne(length); + IntroSort(ref keys, ref values, 0, length - 1, depthLimit, comparer); + } + + private static void IntroSort( + ref TKey keys, ref TValue values, + int lo, int hi, int depthLimit, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + + while (hi > lo) + { + int partitionSize = hi - lo + 1; + if (partitionSize <= IntrosortSizeThreshold) + { + if (partitionSize == 1) + { + return; + } + if (partitionSize == 2) + { + Sort2(ref keys, ref values, lo, hi, comparer); + return; + } + if (partitionSize == 3) + { + Sort3(ref keys, ref values, lo, hi - 1, hi, comparer); + return; + } + InsertionSort(ref keys, ref values, lo, hi, comparer); + return; + } + + if (depthLimit == 0) + { + HeapSort(ref keys, ref values, lo, hi, comparer); + return; + } + depthLimit--; + + // We should never reach here, unless > 3 elements due to partition size + int p = PickPivotAndPartition(ref keys, ref values, lo, hi, comparer); + // Note we've already partitioned around the pivot and do not have to move the pivot again. + IntroSort(ref keys, ref values, p + 1, hi, depthLimit, comparer); + hi = p - 1; + } + } + + private static int PickPivotAndPartition( + ref TKey keys, ref TValue values, int lo, int hi, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + // Compute median-of-three. But also partition them, since we've done the comparison. + + // PERF: `lo` or `hi` will never be negative inside the loop, + // so computing median using uints is safe since we know + // `length <= int.MaxValue`, and indices are >= 0 + // and thus cannot overflow an uint. + // Saves one subtraction per loop compared to + // `int middle = lo + ((hi - lo) >> 1);` + int middle = (int)(((uint)hi + (uint)lo) >> 1); + + // Sort lo, mid and hi appropriately, then pick mid as the pivot. + ref TKey keysAtMiddle = ref Sort3(ref keys, ref values, lo, middle, hi, comparer); + + TKey pivot = keysAtMiddle; + + int left = lo; + int right = hi - 1; + // We already partitioned lo and hi and put the pivot in hi - 1. + // And we pre-increment & decrement below. + Swap(ref keysAtMiddle, ref Unsafe.Add(ref keys, right)); + Swap(ref values, middle, right); + + while (left < right) + { + // TODO: Would be good to be able to update local ref here + + // PERF: For internal direct comparers the range checks are not needed + // since we know they cannot be bogus i.e. pass the pivot without being false. + while (comparer.LessThan(Unsafe.Add(ref keys, ++left), pivot)) ; + while (comparer.LessThan(pivot, Unsafe.Add(ref keys, --right))) ; + + if (left >= right) + break; + + Swap(ref keys, left, right); + Swap(ref values, left, right); + } + // Put pivot in the right location. + right = hi - 1; + if (left != right) + { + Swap(ref keys, left, right); + Swap(ref values, left, right); + } + return left; + } + + private static void HeapSort( + ref TKey keys, ref TValue values, int lo, int hi, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + Debug.Assert(hi > lo); + + int n = hi - lo + 1; + for (int i = n / 2; i >= 1; --i) + { + DownHeap(ref keys, ref values, i, n, lo, comparer); + } + for (int i = n; i > 1; --i) + { + Swap(ref keys, lo, lo + i - 1); + Swap(ref values, lo, lo + i - 1); + DownHeap(ref keys, ref values, 1, i - 1, lo, comparer); + } + } + + private static void DownHeap( + ref TKey keys, ref TValue values, int i, int n, int lo, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(comparer != null); + Debug.Assert(lo >= 0); + + //TKey d = keys[lo + i - 1]; + ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + + ref TValue valuesAtLoMinus1 = ref Unsafe.Add(ref values, lo - 1); + + TKey d = Unsafe.Add(ref keysAtLoMinus1, i); + TValue dValue = Unsafe.Add(ref valuesAtLoMinus1, i); + + var nHalf = n / 2; + while (i <= nHalf) + { + int child = i << 1; + + //if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) + if (child < n && + comparer.LessThan(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child))) + { + ++child; + } + + //if (!(comparer(d, keys[lo + child - 1]) < 0)) + if (!(comparer.LessThan(d, Unsafe.Add(ref keysAtLoMinus1, child)))) + //if (comparer.LessThan(Unsafe.Add(ref keysAtLoMinus1, child), d)) + break; + + // keys[lo + i - 1] = keys[lo + child - 1] + Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); + Unsafe.Add(ref valuesAtLoMinus1, i) = Unsafe.Add(ref valuesAtLoMinus1, child); + + i = child; + } + //keys[lo + i - 1] = d; + Unsafe.Add(ref keysAtLoMinus1, i) = d; + Unsafe.Add(ref valuesAtLoMinus1, i) = dValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InsertionSort( + ref TKey keys, ref TValue values, int lo, int hi, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(lo >= 0); + Debug.Assert(hi >= lo); + + for (int i = lo; i < hi; ++i) + { + int j = i; + //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); + // TODO: Would be good to be able to update local ref here + if (j >= lo && comparer.LessThan(t, Unsafe.Add(ref keys, j))) + { + var v = Unsafe.Add(ref values, j + 1); + do + { + Unsafe.Add(ref keys, j + 1) = Unsafe.Add(ref keys, j); + Unsafe.Add(ref values, j + 1) = Unsafe.Add(ref values, j); + --j; + } + while (j >= lo && comparer.LessThan(t, Unsafe.Add(ref keys, j))); + + Unsafe.Add(ref keys, j + 1) = t; + Unsafe.Add(ref values, j + 1) = v; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ref TKey Sort3( + ref TKey keys, ref TValue values, int i0, int i1, int i2, + TComparer comparer) + where TComparer : IDirectComparer + { + ref var r0 = ref Unsafe.Add(ref keys, i0); + ref var r1 = ref Unsafe.Add(ref keys, i1); + ref var r2 = ref Unsafe.Add(ref keys, i2); + Sort2(ref r0, ref r1, comparer, ref values, i0, i1); + Sort2(ref r0, ref r2, comparer, ref values, i0, i2); + Sort2(ref r1, ref r2, comparer, ref values, i1, i2); + return ref r1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey keys, ref TValue values, int i, int j, + TComparer comparer) + where TComparer : IDirectComparer + { + Debug.Assert(i != j); + + ref TKey a = ref Unsafe.Add(ref keys, i); + ref TKey b = ref Unsafe.Add(ref keys, j); + Sort2(ref a, ref b, comparer, ref values, i, j); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Sort2( + ref TKey a, ref TKey b, TComparer comparer, + ref TValue values, int i, int j) + where TComparer : IDirectComparer + { + // This is one of the only places GreaterThan is needed + // but we need to preserve this due to bogus comparers or similar + if (comparer.GreaterThan(a, b)) + { + Swap(ref a, ref b); + Swap(ref values, i, j); + } + } + } +} diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.cs new file mode 100644 index 000000000000..5d2b4eb586a2 --- /dev/null +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.cs @@ -0,0 +1,243 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if !netstandard +using Internal.Runtime.CompilerServices; +#endif + +using static System.SpanSortHelpersCommon; +using S = System.SpanSortHelpersKeysValues; +using SDC = System.SpanSortHelpersKeysValues_DirectComparer; + +namespace System +{ + internal static partial class SpanSortHelpersKeysValues + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort(this Span keys, Span values) + { + int length = keys.Length; + if (length != values.Length) + ThrowHelper.ThrowArgumentException_ItemsMustHaveSameLength(); + if (length < 2) + return; + + // PERF: Try specialized here for optimal performance + // Code-gen is weird unless used in loop outside + if (!SDC.TrySortSpecialized( + ref MemoryMarshal.GetReference(keys), + ref MemoryMarshal.GetReference(values), + length)) + { + DefaultSpanSortHelper.s_default.Sort( + ref MemoryMarshal.GetReference(keys), + ref MemoryMarshal.GetReference(values), + length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + this Span keys, Span values, TComparer comparer) + where TComparer : IComparer + { + int length = keys.Length; + if (length != values.Length) + ThrowHelper.ThrowArgumentException_ItemsMustHaveSameLength(); + if (length < 2) + return; + + DefaultSpanSortHelper.s_default.Sort( + ref MemoryMarshal.GetReference(keys), + ref MemoryMarshal.GetReference(values), + length, comparer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Sort( + this Span keys, Span values, Comparison comparison) + { + int length = keys.Length; + if (length != values.Length) + ThrowHelper.ThrowArgumentException_ItemsMustHaveSameLength(); + if (length < 2) + return; + + DefaultSpanSortHelper.s_default.Sort( + ref MemoryMarshal.GetReference(keys), + ref MemoryMarshal.GetReference(values), + length, comparison); + } + + internal static class DefaultSpanSortHelper + { + internal static readonly ISpanSortHelper s_default = CreateSortHelper(); + + private static ISpanSortHelper CreateSortHelper() + { + if (typeof(IComparable).IsAssignableFrom(typeof(TKey))) + { + // coreclr uses RuntimeTypeHandle.Allocate + var ctor = typeof(ComparableSpanSortHelper<,>) + .MakeGenericType(new Type[] { typeof(TKey), typeof(TValue) }) + .GetConstructor(Array.Empty()); + + return (ISpanSortHelper)ctor.Invoke(Array.Empty()); + } + else + { + return new SpanSortHelper(); + } + } + } + + internal interface ISpanSortHelper + { + void Sort(ref TKey keys, ref TValue values, int length); + void Sort(ref TKey keys, ref TValue values, int length, Comparison comparison); + } + + internal class SpanSortHelper : ISpanSortHelper + { + public void Sort(ref TKey keys, ref TValue values, int length) + { + S.Sort(ref keys, ref values, length, Comparer.Default); + } + + public void Sort(ref TKey keys, ref TValue values, int length, Comparison comparison) + { + S.Sort(ref keys, ref values, length, comparison); + } + } + + internal class ComparableSpanSortHelper + : ISpanSortHelper + where TKey : IComparable + { + public void Sort(ref TKey keys, ref TValue values, int length) + { + S.Sort(ref keys, ref values, length); + } + + public void Sort(ref TKey keys, ref TValue values, int length, Comparison comparison) + { + // TODO: Check if comparison is Comparer.Default.Compare + + S.Sort(ref keys, ref values, length, comparison); + } + } + + + internal static class DefaultSpanSortHelper + where TComparer : IComparer + { + internal static readonly ISpanSortHelper s_default = CreateSortHelper(); + + private static ISpanSortHelper CreateSortHelper() + { + if (typeof(IComparable).IsAssignableFrom(typeof(TKey))) + { + // coreclr uses RuntimeTypeHandle.Allocate + var ctor = typeof(ComparableSpanSortHelper<,,>) + .MakeGenericType(new Type[] { typeof(TKey), typeof(TValue), typeof(TComparer) }) + .GetConstructor(Array.Empty()); + + return (ISpanSortHelper)ctor.Invoke(Array.Empty()); + } + else + { + return new SpanSortHelper(); + } + } + } + + // https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Collections/Generic/ArraySortHelper.cs + internal interface ISpanSortHelper + where TComparer : IComparer + { + void Sort(ref TKey keys, ref TValue values, int length, TComparer comparer); + } + + internal class SpanSortHelper : ISpanSortHelper + where TComparer : IComparer + { + public void Sort(ref TKey keys, ref TValue values, int length, TComparer comparer) + { + // Add a try block here to detect IComparers (or their + // underlying IComparables, etc) that are bogus. + // + // TODO: Do we need the try/catch? + //try + //{ + if (typeof(TComparer) == typeof(IComparer) && comparer == null) + { + S.Sort(ref keys, ref values, length, Comparer.Default); + } + else + { + S.Sort(ref keys, ref values, length, comparer); + } + //} + //catch (IndexOutOfRangeException e) + //{ + // throw e; + // //IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); + //} + //catch (Exception e) + //{ + // throw e; + // //throw new InvalidOperationException(SR.InvalidOperation_IComparerFailed, e); + //} + } + } + + internal class ComparableSpanSortHelper + : ISpanSortHelper + where TKey : IComparable + where TComparer : IComparer + { + public void Sort(ref TKey keys, ref TValue values, int length, + TComparer comparer) + { + // Add a try block here to detect IComparers (or their + // underlying IComparables, etc) that are bogus. + // + // TODO: Do we need the try/catch? + //try + //{ + if (comparer == null || + // Cache this in generic traits helper class perhaps + (!typeof(TComparer).IsValueType && + object.ReferenceEquals(comparer, Comparer.Default))) // Or "=="? + { + if (!SDC.TrySortSpecialized(ref keys, ref values, length)) + { + // NOTE: For Bogus Comparable the exception message will be different, when using Comparer.Default + // Since the exception message is thrown internally without knowledge of the comparer + S.Sort(ref keys, ref values, length); + } + } + else + { + S.Sort(ref keys, ref values, length, comparer); + } + //} + //catch (IndexOutOfRangeException e) + //{ + // throw e; + // //IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); + //} + //catch (Exception e) + //{ + // throw e; + // //throw new InvalidOperationException(SR.InvalidOperation_IComparerFailed, e); + //} + } + } + } +} From 88810dcd3e4b7408bee3506cfa3aa57412f54fa8 Mon Sep 17 00:00:00 2001 From: nietras Date: Wed, 14 Mar 2018 13:38:31 +0100 Subject: [PATCH 2/8] Add exception string resources. --- src/mscorlib/Resources/Strings.resx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mscorlib/Resources/Strings.resx b/src/mscorlib/Resources/Strings.resx index 28346b2cf572..d3175edc8387 100644 --- a/src/mscorlib/Resources/Strings.resx +++ b/src/mscorlib/Resources/Strings.resx @@ -3718,4 +3718,10 @@ The string must be null-terminated. + + Unable to sort because the IComparable.CompareTo() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparable: '{0}'. + + + Items must have same length as keys + From cd2f5471f14b94b61c5aea92f63b98e56005a11e Mon Sep 17 00:00:00 2001 From: nietras Date: Fri, 16 Mar 2018 10:11:10 +0100 Subject: [PATCH 3/8] Add ThrowHelper stuff --- src/mscorlib/src/System/ThrowHelper.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/mscorlib/src/System/ThrowHelper.cs b/src/mscorlib/src/System/ThrowHelper.cs index 4eb92be31dff..d0af5d7bd633 100644 --- a/src/mscorlib/src/System/ThrowHelper.cs +++ b/src/mscorlib/src/System/ThrowHelper.cs @@ -289,6 +289,30 @@ internal static void ThrowArraySegmentCtorValidationFailedExceptions(Array array throw GetArraySegmentCtorValidationFailedException(array, offset, count); } + internal static void ThrowArgumentException_ItemsMustHaveSameLength() { throw CreateArgumentException_ItemsMustHaveSameLength(); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception CreateArgumentException_ItemsMustHaveSameLength() + { + return GetArgumentException(ExceptionResource.Arg_ItemsMustHaveSameLengthAsKeys); + } + + // coreclr does not have an exception for bad IComparable but instead throws with comparer == null + internal static void ThrowArgumentException_BadComparer(object comparer) { throw CreateArgumentException_BadComparer(comparer); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception CreateArgumentException_BadComparer(object comparer) + { + return new ArgumentException(string.Format(GetResourceString(ExceptionResource.Arg_BogusIComparer), comparer)); + } + + // here we throw if bad comparable, including the case when user uses Comparer.Default and TKey is IComparable + internal static void ThrowArgumentException_BadComparable(Type comparableType) { throw CreateArgumentException_BadComparable(comparableType); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception CreateArgumentException_BadComparable(Type comparableType) + { + return new ArgumentException(string.Format(GetResourceString(ExceptionResource.Arg_BogusIComparable), comparableType.FullName)); + } + + private static Exception GetArraySegmentCtorValidationFailedException(Array array, int offset, int count) { if (array == null) @@ -591,6 +615,8 @@ internal enum ExceptionResource AsyncMethodBuilder_InstanceNotInitialized, ArgumentNull_SafeHandle, NotSupported_StringComparison, + Arg_BogusIComparable, + Arg_ItemsMustHaveSameLengthAsKeys } } From 011c25988079c3838843208fcf5475af5a2ee208 Mon Sep 17 00:00:00 2001 From: nietras Date: Sun, 18 Mar 2018 12:01:18 +0100 Subject: [PATCH 4/8] Address ahsonkhan feedback. --- src/mscorlib/Resources/Strings.resx | 4 ++-- src/mscorlib/shared/System.Private.CoreLib.Shared.projitems | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mscorlib/Resources/Strings.resx b/src/mscorlib/Resources/Strings.resx index d3175edc8387..6ede7eb38a30 100644 --- a/src/mscorlib/Resources/Strings.resx +++ b/src/mscorlib/Resources/Strings.resx @@ -3722,6 +3722,6 @@ Unable to sort because the IComparable.CompareTo() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparable: '{0}'. - Items must have same length as keys + Items must have same length as keys. - + \ No newline at end of file diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index f2b526856303..7595a89cee80 100644 --- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -491,7 +491,6 @@ - @@ -504,6 +503,7 @@ + From c7b9eea583b874f2d992de5f8bb8ed326f5ac0aa Mon Sep 17 00:00:00 2001 From: nietras Date: Sun, 18 Mar 2018 13:13:13 +0100 Subject: [PATCH 5/8] Update to latest corefx changes, mostly cleanup. --- .../shared/System/SpanSortHelpers.Common.cs | 34 +------- .../System/SpanSortHelpers.Keys.Comparison.cs | 80 +++--------------- .../SpanSortHelpers.Keys.IComparable.cs | 62 +++----------- .../System/SpanSortHelpers.Keys.TComparer.cs | 83 +++--------------- .../SpanSortHelpers.Keys.TDirectComparer.cs | 83 +++--------------- .../shared/System/SpanSortHelpers.Keys.cs | 61 +++----------- .../SpanSortHelpers.KeysValues.Comparison.cs | 22 +++-- .../SpanSortHelpers.KeysValues.IComparable.cs | 84 +++---------------- .../SpanSortHelpers.KeysValues.TComparer.cs | 26 +++--- ...nSortHelpers.KeysValues.TDirectComparer.cs | 25 +++--- .../System/SpanSortHelpers.KeysValues.cs | 60 +++---------- 11 files changed, 110 insertions(+), 510 deletions(-) diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Common.cs b/src/mscorlib/shared/System/SpanSortHelpers.Common.cs index edeffa2c2b79..0abe392eba2f 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.Common.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.Common.cs @@ -7,10 +7,8 @@ namespace System { - // TODO: Rename to SpanSortHelpers before move to corefx internal static partial class SpanSortHelpersCommon { - // 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. @@ -51,8 +49,8 @@ internal interface IDirectComparer { 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 } + // // Type specific DirectComparer(s) to ensure optimal code-gen // @@ -62,8 +60,6 @@ internal struct SByteDirectComparer : IDirectComparer 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 { @@ -71,8 +67,6 @@ internal struct ByteDirectComparer : IDirectComparer 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 { @@ -80,8 +74,6 @@ internal struct Int16DirectComparer : IDirectComparer 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 { @@ -89,8 +81,6 @@ internal struct UInt16DirectComparer : IDirectComparer 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 { @@ -98,8 +88,6 @@ internal struct Int32DirectComparer : IDirectComparer 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 { @@ -107,8 +95,6 @@ internal struct UInt32DirectComparer : IDirectComparer 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; } internal struct Int64DirectComparer : IDirectComparer { @@ -116,8 +102,6 @@ internal struct Int64DirectComparer : IDirectComparer 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 { @@ -125,8 +109,6 @@ internal struct UInt64DirectComparer : IDirectComparer 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 { @@ -134,8 +116,6 @@ internal struct SingleDirectComparer : IDirectComparer 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 { @@ -143,18 +123,6 @@ internal struct DoubleDirectComparer : IDirectComparer 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 - { - [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; } internal interface IIsNaN diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.Comparison.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.Comparison.cs index a5534546bb67..05873c804b85 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.Keys.Comparison.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.Comparison.cs @@ -27,7 +27,7 @@ private static void IntrospectiveSort( ref TKey keys, int length, Comparison comparison) { - var depthLimit = 2 * FloorLog2PlusOne(length); + int depthLimit = 2 * FloorLog2PlusOne(length); IntroSort(ref keys, 0, length - 1, depthLimit, comparison); } @@ -58,7 +58,6 @@ private static void IntroSort( ref TKey loRef = ref Unsafe.Add(ref keys, lo); ref TKey miRef = ref Unsafe.Add(ref keys, hi - 1); ref TKey hiRef = ref Unsafe.Add(ref keys, hi); - //ref TKey miRef = ref Unsafe.SubtractByteOffset(ref hiRef, new IntPtr(Unsafe.SizeOf())); Sort3(ref loRef, ref miRef, ref hiRef, comparison); return; } @@ -75,6 +74,8 @@ private static void IntroSort( depthLimit--; // We should never reach here, unless > 3 elements due to partition size + Debug.Assert(partitionSize > 3); + int p = PickPivotAndPartition(ref keys, lo, hi, comparison); // Note we've already partitioned around the pivot and do not have to move the pivot again. IntroSort(ref keys, p + 1, hi, depthLimit, comparison); @@ -116,8 +117,6 @@ private static int PickPivotAndPartition( while (left < right) { - // TODO: Would be good to be able to update local ref here - while (left < (hi - 1) && comparison(Unsafe.Add(ref keys, ++left), pivot) < 0) ; // Check if bad comparable/comparison if (left == (hi - 1) && comparison(Unsafe.Add(ref keys, left), pivot) < 0) @@ -171,11 +170,11 @@ private static void DownHeap( Debug.Assert(comparison != null); Debug.Assert(lo >= 0); - //TKey d = keys[lo + i - 1]; + // Below lines are equivalent to: TKey d = keys[lo + i - 1]; ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); - ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // TODO: Use Subtract when available TKey d = Unsafe.Add(ref keysAtLoMinus1, i); - var nHalf = n / 2; + int nHalf = n / 2; while (i <= nHalf) { int child = i << 1; @@ -191,12 +190,12 @@ private static void DownHeap( if (!(comparison(d, Unsafe.Add(ref keysAtLoMinus1, child)) < 0)) break; - // keys[lo + i - 1] = keys[lo + child - 1] + // Below lines are equivalent to: keys[lo + i - 1] = keys[lo + child - 1] Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); i = child; } - //keys[lo + i - 1] = d; + // Below lines are equivalent to: keys[lo + i - 1] = d; Unsafe.Add(ref keysAtLoMinus1, i) = d; } @@ -212,9 +211,9 @@ private static void InsertionSort( for (int i = lo; i < hi; ++i) { int j = i; - //t = keys[i + 1]; - var t = Unsafe.Add(ref keys, j + 1); - // TODO: Would be good to be able to update local ref here + + TKey t = Unsafe.Add(ref keys, j + 1); + if (j >= lo && comparison(t, Unsafe.Add(ref keys, j)) < 0) { do @@ -238,63 +237,6 @@ private static void Sort3( Sort2(ref r0, ref r1, comparison); Sort2(ref r0, ref r2, comparison); Sort2(ref r1, ref r2, comparison); - - // Below works but does not give exactly the same result as Array.Sort - // i.e. order could be a bit different for keys that are equal - //if (comparison.LessThanEqual(r0, r1)) - //{ - // // r0 <= r1 - // if (comparison.LessThanEqual(r1, r2)) - // { - // // r0 <= r1 <= r2 - // return; // Is this return good or bad for perf? - // } - // // r0 <= r1 - // // r2 < r1 - // else if (comparison.LessThanEqual(r0, r2)) - // { - // // r0 <= r2 < r1 - // Swap(ref r1, ref r2); - // } - // // r0 <= r1 - // // r2 < r1 - // // r2 < r0 - // else - // { - // // r2 < r0 <= r1 - // TKey tmp = r0; - // r0 = r2; - // r2 = r1; - // r1 = tmp; - // } - //} - //else - //{ - // // r1 < r0 - // if (comparison.LessThan(r2, r1)) - // { - // // r2 < r1 < r0 - // Swap(ref r0, ref r2); - // } - // // r1 < r0 - // // r1 <= r2 - // else if (comparison.LessThan(r2, r0)) - // { - // // r1 <= r2 < r0 - // TKey tmp = r0; - // r0 = r1; - // r1 = r2; - // r2 = tmp; - // } - // // r1 < r0 - // // r1 <= r2 - // // r0 <= r2 - // else - // { - // // r1 < r0 <= r2 - // Swap(ref r0, ref r1); - // } - //} } diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.IComparable.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.IComparable.cs index 8c18d5171ebd..54932f1db393 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.Keys.IComparable.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.IComparable.cs @@ -27,7 +27,7 @@ private static void IntrospectiveSort( ref TKey keys, int length) where TKey : IComparable { - var depthLimit = 2 * FloorLog2PlusOne(length); + int depthLimit = 2 * FloorLog2PlusOne(length); IntroSort(ref keys, 0, length - 1, depthLimit); } @@ -57,7 +57,6 @@ private static void IntroSort( ref TKey loRef = ref Unsafe.Add(ref keys, lo); ref TKey miRef = ref Unsafe.Add(ref keys, hi - 1); ref TKey hiRef = ref Unsafe.Add(ref keys, hi); - //ref TKey miRef = ref Unsafe.SubtractByteOffset(ref hiRef, new IntPtr(Unsafe.SizeOf())); Sort3(ref loRef, ref miRef, ref hiRef); return; } @@ -74,6 +73,8 @@ private static void IntroSort( depthLimit--; // We should never reach here, unless > 3 elements due to partition size + Debug.Assert(partitionSize > 3); + int p = PickPivotAndPartition(ref keys, lo, hi); // Note we've already partitioned around the pivot and do not have to move the pivot again. IntroSort(ref keys, p + 1, hi, depthLimit); @@ -115,8 +116,6 @@ private static int PickPivotAndPartition( while (left < right) { - // TODO: Would be good to be able to update local ref here - if (pivot == null) { while (left < (hi - 1) && Unsafe.Add(ref keys, ++left) == null) ; @@ -176,11 +175,11 @@ private static void DownHeap( Debug.Assert(lo >= 0); - //TKey d = keys[lo + i - 1]; + // Below lines are equivalent to: TKey d = keys[lo + i - 1]; ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); - ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // TODO: Use Subtract when available TKey d = Unsafe.Add(ref keysAtLoMinus1, i); - var nHalf = n / 2; + int nHalf = n / 2; while (i <= nHalf) { int child = i << 1; @@ -198,12 +197,12 @@ private static void DownHeap( Unsafe.Add(ref keysAtLoMinus1, child).CompareTo(d) < 0) break; - // keys[lo + i - 1] = keys[lo + child - 1] + // Below lines are equivalent to: keys[lo + i - 1] = keys[lo + child - 1] Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); i = child; } - //keys[lo + i - 1] = d; + // Below lines are equivalent to: keys[lo + i - 1] = d; Unsafe.Add(ref keysAtLoMinus1, i) = d; } @@ -218,9 +217,9 @@ private static void InsertionSort( for (int i = lo; i < hi; ++i) { int j = i; - //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); - // TODO: Would be good to be able to update local ref here + if (j >= lo && (t == null || t.CompareTo(Unsafe.Add(ref keys, j)) < 0)) { do @@ -229,7 +228,6 @@ private static void InsertionSort( --j; } while (j >= lo && (t == null || t.CompareTo(Unsafe.Add(ref keys, j)) < 0)); - //while (j >= lo && (t == null || t.CompareTo(keys[j]) < 0)) Unsafe.Add(ref keys, j + 1) = t; } @@ -244,48 +242,8 @@ private static void Sort3( Sort2(ref r0, ref r1); Sort2(ref r0, ref r2); Sort2(ref r1, ref r2); - - // Below works but does not give exactly the same result as Array.Sort - // i.e. order could be a bit different for keys that are equal - //if (r0 != null && r0.CompareTo(r1) <= 0) //r0 <= r1) - //{ - // if (r1 != null && r1.CompareTo(r2) <= 0) //(r1 <= r2) - // { - // return; - // } - // else if (r0.CompareTo(r2) < 0) //(r0 < r2) - // { - // Swap(ref r1, ref r2); - // } - // else - // { - // TKey tmp = r0; - // r0 = r2; - // r2 = r1; - // r1 = tmp; - // } - //} - //else - //{ - // if (r0 != null && r0.CompareTo(r2) < 0) //(r0 < r2) - // { - // Swap(ref r0, ref r1); - // } - // else if (r2 != null && r2.CompareTo(r1) < 0) //(r2 < r1) - // { - // Swap(ref r0, ref r2); - // } - // else - // { - // TKey tmp = r0; - // r0 = r1; - // r1 = r2; - // r2 = tmp; - // } - //} } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Sort2( ref TKey keys, int i, int j) diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.TComparer.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.TComparer.cs index 30c1a3f0fd3a..6e68e00b51d7 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.Keys.TComparer.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.TComparer.cs @@ -30,7 +30,7 @@ private static void IntrospectiveSort( TComparer comparer) where TComparer : IComparer { - var depthLimit = 2 * FloorLog2PlusOne(length); + int depthLimit = 2 * FloorLog2PlusOne(length); IntroSort(ref keys, 0, length - 1, depthLimit, comparer); } @@ -62,7 +62,6 @@ private static void IntroSort( ref TKey loRef = ref Unsafe.Add(ref keys, lo); ref TKey miRef = ref Unsafe.Add(ref keys, hi - 1); ref TKey hiRef = ref Unsafe.Add(ref keys, hi); - //ref TKey miRef = ref Unsafe.SubtractByteOffset(ref hiRef, new IntPtr(Unsafe.SizeOf())); Sort3(ref loRef, ref miRef, ref hiRef, comparer); return; } @@ -79,6 +78,8 @@ private static void IntroSort( depthLimit--; // We should never reach here, unless > 3 elements due to partition size + Debug.Assert(partitionSize > 3); + int p = PickPivotAndPartition(ref keys, lo, hi, comparer); // Note we've already partitioned around the pivot and do not have to move the pivot again. IntroSort(ref keys, p + 1, hi, depthLimit, comparer); @@ -121,8 +122,6 @@ private static int PickPivotAndPartition( while (left < right) { - // TODO: Would be good to be able to update local ref here - while (left < (hi - 1) && comparer.Compare(Unsafe.Add(ref keys, ++left), pivot) < 0) ; // Check if bad comparable/comparer if (left == (hi - 1) && comparer.Compare(Unsafe.Add(ref keys, left), pivot) < 0) @@ -176,32 +175,32 @@ private static void DownHeap( Debug.Assert(comparer != null); Debug.Assert(lo >= 0); - //TKey d = keys[lo + i - 1]; + // Below lines are equivalent to: TKey d = keys[lo + i - 1]; ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); - ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // TODO: Use Subtract when available TKey d = Unsafe.Add(ref keysAtLoMinus1, i); - var nHalf = n / 2; + int nHalf = n / 2; while (i <= nHalf) { int child = i << 1; - //if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) + // Below lines are equivalent to: if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) if (child < n && comparer.Compare(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child)) < 0) { ++child; } - //if (!(comparer(d, keys[lo + child - 1]) < 0)) + // Below lines are equivalent to: if (!(comparer(d, keys[lo + child - 1]) < 0)) if (!(comparer.Compare(d, Unsafe.Add(ref keysAtLoMinus1, child)) < 0)) break; - // keys[lo + i - 1] = keys[lo + child - 1] + // Below lines are equivalent to: keys[lo + i - 1] = keys[lo + child - 1] Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); i = child; } - //keys[lo + i - 1] = d; + // Below lines are equivalent to: keys[lo + i - 1] = d; Unsafe.Add(ref keysAtLoMinus1, i) = d; } @@ -217,9 +216,9 @@ private static void InsertionSort( for (int i = lo; i < hi; ++i) { int j = i; - //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); - // TODO: Would be good to be able to update local ref here + if (j >= lo && comparer.Compare(t, Unsafe.Add(ref keys, j)) < 0) { do @@ -243,66 +242,8 @@ private static void Sort3( Sort2(ref r0, ref r1, comparer); Sort2(ref r0, ref r2, comparer); Sort2(ref r1, ref r2, comparer); - - // Below works but does not give exactly the same result as Array.Sort - // i.e. order could be a bit different for keys that are equal - //if (comparer.LessThanEqual(r0, r1)) - //{ - // // r0 <= r1 - // if (comparer.LessThanEqual(r1, r2)) - // { - // // r0 <= r1 <= r2 - // return; // Is this return good or bad for perf? - // } - // // r0 <= r1 - // // r2 < r1 - // else if (comparer.LessThanEqual(r0, r2)) - // { - // // r0 <= r2 < r1 - // Swap(ref r1, ref r2); - // } - // // r0 <= r1 - // // r2 < r1 - // // r2 < r0 - // else - // { - // // r2 < r0 <= r1 - // TKey tmp = r0; - // r0 = r2; - // r2 = r1; - // r1 = tmp; - // } - //} - //else - //{ - // // r1 < r0 - // if (comparer.LessThan(r2, r1)) - // { - // // r2 < r1 < r0 - // Swap(ref r0, ref r2); - // } - // // r1 < r0 - // // r1 <= r2 - // else if (comparer.LessThan(r2, r0)) - // { - // // r1 <= r2 < r0 - // TKey tmp = r0; - // r0 = r1; - // r1 = r2; - // r2 = tmp; - // } - // // r1 < r0 - // // r1 <= r2 - // // r0 <= r2 - // else - // { - // // r1 < r0 <= r2 - // Swap(ref r0, ref r1); - // } - //} } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Sort2( ref TKey keys, int i, int j, diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.TDirectComparer.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.TDirectComparer.cs index 54e2c376b178..a92991656eac 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.Keys.TDirectComparer.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.TDirectComparer.cs @@ -29,7 +29,7 @@ private static void IntrospectiveSort( TComparer comparer) where TComparer : IDirectComparer { - var depthLimit = 2 * FloorLog2PlusOne(length); + int depthLimit = 2 * FloorLog2PlusOne(length); IntroSort(ref keys, 0, length - 1, depthLimit, comparer); } @@ -61,7 +61,6 @@ private static void IntroSort( ref TKey loRef = ref Unsafe.Add(ref keys, lo); ref TKey miRef = ref Unsafe.Add(ref keys, hi - 1); ref TKey hiRef = ref Unsafe.Add(ref keys, hi); - //ref TKey miRef = ref Unsafe.SubtractByteOffset(ref hiRef, new IntPtr(Unsafe.SizeOf())); Sort3(ref loRef, ref miRef, ref hiRef, comparer); return; } @@ -78,6 +77,8 @@ private static void IntroSort( depthLimit--; // We should never reach here, unless > 3 elements due to partition size + Debug.Assert(partitionSize > 3); + int p = PickPivotAndPartition(ref keys, lo, hi, comparer); // Note we've already partitioned around the pivot and do not have to move the pivot again. IntroSort(ref keys, p + 1, hi, depthLimit, comparer); @@ -120,8 +121,6 @@ private static int PickPivotAndPartition( while (left < right) { - // TODO: Would be good to be able to update local ref here - // PERF: For internal direct comparers the range checks are not needed // since we know they cannot be bogus i.e. pass the pivot without being false. while (comparer.LessThan(Unsafe.Add(ref keys, ++left), pivot)) ; @@ -170,32 +169,32 @@ private static void DownHeap( Debug.Assert(comparer != null); Debug.Assert(lo >= 0); - //TKey d = keys[lo + i - 1]; + // Below lines are equivalent to: TKey d = keys[lo + i - 1]; ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); - ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // TODO: Use Subtract when available TKey d = Unsafe.Add(ref keysAtLoMinus1, i); - var nHalf = n / 2; + int nHalf = n / 2; while (i <= nHalf) { int child = i << 1; - //if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) + // Below lines are equivalent to: if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) if (child < n && comparer.LessThan(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child))) { ++child; } - //if (!(comparer(d, keys[lo + child - 1]) < 0)) + // Below lines are equivalent to: if (!(comparer(d, keys[lo + child - 1]) < 0)) if (!(comparer.LessThan(d, Unsafe.Add(ref keysAtLoMinus1, child)))) break; - // keys[lo + i - 1] = keys[lo + child - 1] + // Below lines are equivalent to: keys[lo + i - 1] = keys[lo + child - 1] Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); i = child; } - //keys[lo + i - 1] = d; + // Below lines are equivalent to: keys[lo + i - 1] = d; Unsafe.Add(ref keysAtLoMinus1, i) = d; } @@ -211,9 +210,9 @@ private static void InsertionSort( for (int i = lo; i < hi; ++i) { int j = i; - //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); - // TODO: Would be good to be able to update local ref here + if (j >= lo && comparer.LessThan(t, Unsafe.Add(ref keys, j))) { do @@ -237,66 +236,8 @@ private static void Sort3( Sort2(ref r0, ref r1, comparer); Sort2(ref r0, ref r2, comparer); Sort2(ref r1, ref r2, comparer); - - // Below works but does not give exactly the same result as Array.Sort - // i.e. order could be a bit different for keys that are equal - //if (comparer.LessThanEqual(r0, r1)) - //{ - // // r0 <= r1 - // if (comparer.LessThanEqual(r1, r2)) - // { - // // r0 <= r1 <= r2 - // return; // Is this return good or bad for perf? - // } - // // r0 <= r1 - // // r2 < r1 - // else if (comparer.LessThanEqual(r0, r2)) - // { - // // r0 <= r2 < r1 - // Swap(ref r1, ref r2); - // } - // // r0 <= r1 - // // r2 < r1 - // // r2 < r0 - // else - // { - // // r2 < r0 <= r1 - // TKey tmp = r0; - // r0 = r2; - // r2 = r1; - // r1 = tmp; - // } - //} - //else - //{ - // // r1 < r0 - // if (comparer.LessThan(r2, r1)) - // { - // // r2 < r1 < r0 - // Swap(ref r0, ref r2); - // } - // // r1 < r0 - // // r1 <= r2 - // else if (comparer.LessThan(r2, r0)) - // { - // // r1 <= r2 < r0 - // TKey tmp = r0; - // r0 = r1; - // r1 = r2; - // r2 = tmp; - // } - // // r1 < r0 - // // r1 <= r2 - // // r0 <= r2 - // else - // { - // // r1 < r0 <= r2 - // Swap(ref r0, ref r1); - // } - //} } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Sort2( ref TKey keys, int i, int j, diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Keys.cs b/src/mscorlib/shared/System/SpanSortHelpers.Keys.cs index 7a68cb0b6529..151b0b6ce72e 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.Keys.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.Keys.cs @@ -10,11 +10,6 @@ using Internal.Runtime.CompilerServices; #endif -using static System.SpanSortHelpersCommon; -using S = System.SpanSortHelpersKeys; -using SC = System.SpanSortHelpersKeys_Comparer; -using SDC = System.SpanSortHelpersKeys_DirectComparer; - namespace System { internal static partial class SpanSortHelpersKeys @@ -28,7 +23,7 @@ internal static void Sort(this Span keys) // PERF: Try specialized here for optimal performance // Code-gen is weird unless used in loop outside - if (!SDC.TrySortSpecialized( + if (!SpanSortHelpersKeys_DirectComparer.TrySortSpecialized( ref MemoryMarshal.GetReference(keys), length)) { @@ -97,12 +92,12 @@ internal class SpanSortHelper : ISpanSortHelper { public void Sort(ref TKey keys, int length) { - SC.Sort(ref keys, length, Comparer.Default); + SpanSortHelpersKeys_Comparer.Sort(ref keys, length, Comparer.Default); } public void Sort(ref TKey keys, int length, Comparison comparison) { - S.Sort(ref keys, length, comparison); + SpanSortHelpersKeys.Sort(ref keys, length, comparison); } } @@ -112,14 +107,12 @@ internal class ComparableSpanSortHelper { public void Sort(ref TKey keys, int length) { - S.Sort(ref keys, length); + SpanSortHelpersKeys.Sort(ref keys, length); } public void Sort(ref TKey keys, int length, Comparison comparison) { - // TODO: Check if comparison is Comparer.Default.Compare - - S.Sort(ref keys, length, comparison); + SpanSortHelpersKeys.Sort(ref keys, length, comparison); } } @@ -159,31 +152,14 @@ internal class SpanSortHelper : ISpanSortHelper) && comparer == null) { - SC.Sort(ref keys, length, Comparer.Default); + SpanSortHelpersKeys_Comparer.Sort(ref keys, length, Comparer.Default); } else { - SC.Sort(ref keys, length, comparer); + SpanSortHelpersKeys_Comparer.Sort(ref keys, length, comparer); } - //} - //catch (IndexOutOfRangeException e) - //{ - // throw e; - // //IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); - //} - //catch (Exception e) - //{ - // throw e; - // //throw new InvalidOperationException(SR.InvalidOperation_IComparerFailed, e); - //} } } @@ -195,39 +171,22 @@ internal class ComparableSpanSortHelper public void Sort(ref TKey keys, int length, TComparer comparer) { - // Add a try block here to detect IComparers (or their - // underlying IComparables, etc) that are bogus. - // - // TODO: Do we need the try/catch? - //try - //{ if (comparer == null || // Cache this in generic traits helper class perhaps (!typeof(TComparer).IsValueType && object.ReferenceEquals(comparer, Comparer.Default))) // Or "=="? { - if (!SDC.TrySortSpecialized(ref keys, length)) + if (!SpanSortHelpersKeys_DirectComparer.TrySortSpecialized(ref keys, length)) { // NOTE: For Bogus Comparable the exception message will be different, when using Comparer.Default // Since the exception message is thrown internally without knowledge of the comparer - S.Sort(ref keys, length); + SpanSortHelpersKeys.Sort(ref keys, length); } } else { - SC.Sort(ref keys, length, comparer); + SpanSortHelpersKeys_Comparer.Sort(ref keys, length, comparer); } - //} - //catch (IndexOutOfRangeException e) - //{ - // throw e; - // //IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); - //} - //catch (Exception e) - //{ - // throw e; - // //throw new InvalidOperationException(SR.InvalidOperation_IComparerFailed, e); - //} } } } diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Comparison.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Comparison.cs index efc8c50b381f..6c2af8e8cf81 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Comparison.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.Comparison.cs @@ -27,7 +27,7 @@ private static void IntrospectiveSort( ref TKey keys, ref TValue values, int length, Comparison comparison) { - var depthLimit = 2 * FloorLog2PlusOne(length); + int depthLimit = 2 * FloorLog2PlusOne(length); IntroSort(ref keys, ref values, 0, length - 1, depthLimit, comparison); } @@ -70,6 +70,8 @@ private static void IntroSort( depthLimit--; // We should never reach here, unless > 3 elements due to partition size + Debug.Assert(partitionSize > 3); + int p = PickPivotAndPartition(ref keys, ref values, lo, hi, comparison); // Note we've already partitioned around the pivot and do not have to move the pivot again. IntroSort(ref keys, ref values, p + 1, hi, depthLimit, comparison); @@ -110,10 +112,6 @@ private static int PickPivotAndPartition( while (left < right) { - // TODO: Would be good to be able to update local ref here - - // TODO: For primitives and internal comparers the range checks can be eliminated - while (left < (hi - 1) && comparison(Unsafe.Add(ref keys, ++left), pivot) < 0) ; // Check if bad comparable/comparison if (left == (hi - 1) && comparison(Unsafe.Add(ref keys, left), pivot) < 0) @@ -170,16 +168,16 @@ private static void DownHeap( Debug.Assert(comparison != null); Debug.Assert(lo >= 0); - //TKey d = keys[lo + i - 1]; + // Below lines are equivalent to: TKey d = keys[lo + i - 1]; ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); - ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // TODO: Use Subtract when available ref TValue valuesAtLoMinus1 = ref Unsafe.Add(ref values, lo - 1); TKey d = Unsafe.Add(ref keysAtLoMinus1, i); TValue dValue = Unsafe.Add(ref valuesAtLoMinus1, i); - var nHalf = n / 2; + int nHalf = n / 2; while (i <= nHalf) { int child = i << 1; @@ -195,13 +193,13 @@ private static void DownHeap( if (!(comparison(d, Unsafe.Add(ref keysAtLoMinus1, child)) < 0)) break; - // keys[lo + i - 1] = keys[lo + child - 1] + // Below lines are equivalent to: keys[lo + i - 1] = keys[lo + child - 1] Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); Unsafe.Add(ref valuesAtLoMinus1, i) = Unsafe.Add(ref valuesAtLoMinus1, child); i = child; } - //keys[lo + i - 1] = d; + // Below lines are equivalent to: keys[lo + i - 1] = d; Unsafe.Add(ref keysAtLoMinus1, i) = d; Unsafe.Add(ref valuesAtLoMinus1, i) = dValue; } @@ -218,9 +216,9 @@ private static void InsertionSort( for (int i = lo; i < hi; ++i) { int j = i; - //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); - // TODO: Would be good to be able to update local ref here + if (j >= lo && comparison(t, Unsafe.Add(ref keys, j)) < 0) { var v = Unsafe.Add(ref values, j + 1); diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.IComparable.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.IComparable.cs index ba3b68c3bd07..fef473ac76fb 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.IComparable.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.IComparable.cs @@ -27,7 +27,7 @@ private static void IntrospectiveSort( ref TKey keys, ref TValue values, int length) where TKey : IComparable { - var depthLimit = 2 * FloorLog2PlusOne(length); + int depthLimit = 2 * FloorLog2PlusOne(length); IntroSort(ref keys, ref values, 0, length - 1, depthLimit); } @@ -69,6 +69,8 @@ private static void IntroSort( depthLimit--; // We should never reach here, unless > 3 elements due to partition size + Debug.Assert(partitionSize > 3); + int p = PickPivotAndPartition(ref keys, ref values, lo, hi); // Note we've already partitioned around the pivot and do not have to move the pivot again. IntroSort(ref keys, ref values, p + 1, hi, depthLimit); @@ -108,8 +110,6 @@ private static int PickPivotAndPartition( while (left < right) { - // TODO: Would be good to be able to update local ref here - if (pivot == null) { while (left < (hi - 1) && Unsafe.Add(ref keys, ++left) == null) ; @@ -173,16 +173,16 @@ private static void DownHeap( Debug.Assert(lo >= 0); - //TKey d = keys[lo + i - 1]; + // Below lines are equivalent to: TKey d = keys[lo + i - 1]; ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); - ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // TODO: Use Subtract when available ref TValue valuesAtLoMinus1 = ref Unsafe.Add(ref values, lo - 1); TKey d = Unsafe.Add(ref keysAtLoMinus1, i); TValue dValue = Unsafe.Add(ref valuesAtLoMinus1, i); - var nHalf = n / 2; + int nHalf = n / 2; while (i <= nHalf) { int child = i << 1; @@ -200,13 +200,13 @@ private static void DownHeap( Unsafe.Add(ref keysAtLoMinus1, child).CompareTo(d) < 0) break; - // keys[lo + i - 1] = keys[lo + child - 1] + // Below lines are equivalent to: keys[lo + i - 1] = keys[lo + child - 1] Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); Unsafe.Add(ref valuesAtLoMinus1, i) = Unsafe.Add(ref valuesAtLoMinus1, child); i = child; } - //keys[lo + i - 1] = d; + // Below lines are equivalent to: keys[lo + i - 1] = d; Unsafe.Add(ref keysAtLoMinus1, i) = d; Unsafe.Add(ref valuesAtLoMinus1, i) = dValue; } @@ -222,9 +222,9 @@ private static void InsertionSort( for (int i = lo; i < hi; ++i) { int j = i; - //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); - // TODO: Would be good to be able to update local ref here + if (j >= lo && (t == null || t.CompareTo(Unsafe.Add(ref keys, j)) < 0)) { var v = Unsafe.Add(ref values, j + 1); @@ -254,70 +254,6 @@ private static ref TKey Sort3( Sort2(ref r0, ref r1, ref values, i0, i1); Sort2(ref r0, ref r2, ref values, i0, i2); Sort2(ref r1, ref r2, ref values, i1, i2); - - //ref var r0 = ref Unsafe.Add(ref keys, i0); - //ref var r1 = ref Unsafe.Add(ref keys, i1); - //ref var r2 = ref Unsafe.Add(ref keys, i2); - - //if (r0 != null && r0.CompareTo(r1) <= 0) //r0 <= r1) - //{ - // if (r1 != null && r1.CompareTo(r2) <= 0) //(r1 <= r2) - // { - // return ref r1; - // } - // else if (r0.CompareTo(r2) < 0) //(r0 < r2) - // { - // Swap(ref r1, ref r2); - // ref var v1 = ref Unsafe.Add(ref values, i1); - // ref var v2 = ref Unsafe.Add(ref values, i2); - // Swap(ref v1, ref v2); - // } - // else - // { - // TKey tmp = r0; - // r0 = r2; - // r2 = r1; - // r1 = tmp; - // ref var v0 = ref Unsafe.Add(ref values, i0); - // ref var v1 = ref Unsafe.Add(ref values, i1); - // ref var v2 = ref Unsafe.Add(ref values, i2); - // TValue vTemp = v0; - // v0 = v2; - // v2 = v1; - // v1 = vTemp; - // } - //} - //else - //{ - // if (r0 != null && r0.CompareTo(r2) < 0) //(r0 < r2) - // { - // Swap(ref r0, ref r1); - // ref var v0 = ref Unsafe.Add(ref values, i0); - // ref var v1 = ref Unsafe.Add(ref values, i1); - // Swap(ref v0, ref v1); - // } - // else if (r2 != null && r2.CompareTo(r1) < 0) //(r2 < r1) - // { - // Swap(ref r0, ref r2); - // ref var v0 = ref Unsafe.Add(ref values, i0); - // ref var v2 = ref Unsafe.Add(ref values, i2); - // Swap(ref v0, ref v2); - // } - // else - // { - // TKey tmp = r0; - // r0 = r1; - // r1 = r2; - // r2 = tmp; - // ref var v0 = ref Unsafe.Add(ref values, i0); - // ref var v1 = ref Unsafe.Add(ref values, i1); - // ref var v2 = ref Unsafe.Add(ref values, i2); - // TValue vTemp = v0; - // v0 = v1; - // v1 = v2; - // v2 = vTemp; - // } - //} return ref r1; } diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TComparer.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TComparer.cs index 89c1f5d3117f..38eefeb4dd07 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TComparer.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TComparer.cs @@ -30,7 +30,7 @@ private static void IntrospectiveSort( TComparer comparer) where TComparer : IComparer { - var depthLimit = 2 * FloorLog2PlusOne(length); + int depthLimit = 2 * FloorLog2PlusOne(length); IntroSort(ref keys, ref values, 0, length - 1, depthLimit, comparer); } @@ -74,6 +74,8 @@ private static void IntroSort( depthLimit--; // We should never reach here, unless > 3 elements due to partition size + Debug.Assert(partitionSize > 3); + int p = PickPivotAndPartition(ref keys, ref values, lo, hi, comparer); // Note we've already partitioned around the pivot and do not have to move the pivot again. IntroSort(ref keys, ref values, p + 1, hi, depthLimit, comparer); @@ -114,10 +116,6 @@ private static int PickPivotAndPartition( while (left < right) { - // TODO: Would be good to be able to update local ref here - - // TODO: For primitives and internal comparers the range checks can be eliminated - while (left < (hi - 1) && comparer.Compare(Unsafe.Add(ref keys, ++left), pivot) < 0) ; // Check if bad comparable/comparer if (left == (hi - 1) && comparer.Compare(Unsafe.Add(ref keys, left), pivot) < 0) @@ -174,38 +172,38 @@ private static void DownHeap( Debug.Assert(comparer != null); Debug.Assert(lo >= 0); - //TKey d = keys[lo + i - 1]; + // Below lines are equivalent to: TKey d = keys[lo + i - 1]; ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); - ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // TODO: Use Subtract when available ref TValue valuesAtLoMinus1 = ref Unsafe.Add(ref values, lo - 1); TKey d = Unsafe.Add(ref keysAtLoMinus1, i); TValue dValue = Unsafe.Add(ref valuesAtLoMinus1, i); - var nHalf = n / 2; + int nHalf = n / 2; while (i <= nHalf) { int child = i << 1; - //if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) + // Below lines are equivalent to: if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) if (child < n && comparer.Compare(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child)) < 0) { ++child; } - //if (!(comparer(d, keys[lo + child - 1]) < 0)) + // Below lines are equivalent to: if (!(comparer(d, keys[lo + child - 1]) < 0)) if (!(comparer.Compare(d, Unsafe.Add(ref keysAtLoMinus1, child)) < 0)) break; - // keys[lo + i - 1] = keys[lo + child - 1] + // Below lines are equivalent to: keys[lo + i - 1] = keys[lo + child - 1] Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); Unsafe.Add(ref valuesAtLoMinus1, i) = Unsafe.Add(ref valuesAtLoMinus1, child); i = child; } - //keys[lo + i - 1] = d; + // Below lines are equivalent to: keys[lo + i - 1] = d; Unsafe.Add(ref keysAtLoMinus1, i) = d; Unsafe.Add(ref valuesAtLoMinus1, i) = dValue; } @@ -222,9 +220,9 @@ private static void InsertionSort( for (int i = lo; i < hi; ++i) { int j = i; - //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); - // TODO: Would be good to be able to update local ref here + if (j >= lo && comparer.Compare(t, Unsafe.Add(ref keys, j)) < 0) { var v = Unsafe.Add(ref values, j + 1); diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TDirectComparer.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TDirectComparer.cs index 971e5c689fe9..2897f9dc862b 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TDirectComparer.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.TDirectComparer.cs @@ -29,7 +29,7 @@ private static void IntrospectiveSort( TComparer comparer) where TComparer : IDirectComparer { - var depthLimit = 2 * FloorLog2PlusOne(length); + int depthLimit = 2 * FloorLog2PlusOne(length); IntroSort(ref keys, ref values, 0, length - 1, depthLimit, comparer); } @@ -73,6 +73,8 @@ private static void IntroSort( depthLimit--; // We should never reach here, unless > 3 elements due to partition size + Debug.Assert(partitionSize > 3); + int p = PickPivotAndPartition(ref keys, ref values, lo, hi, comparer); // Note we've already partitioned around the pivot and do not have to move the pivot again. IntroSort(ref keys, ref values, p + 1, hi, depthLimit, comparer); @@ -113,8 +115,6 @@ private static int PickPivotAndPartition( while (left < right) { - // TODO: Would be good to be able to update local ref here - // PERF: For internal direct comparers the range checks are not needed // since we know they cannot be bogus i.e. pass the pivot without being false. while (comparer.LessThan(Unsafe.Add(ref keys, ++left), pivot)) ; @@ -166,39 +166,38 @@ private static void DownHeap( Debug.Assert(comparer != null); Debug.Assert(lo >= 0); - //TKey d = keys[lo + i - 1]; + // Below lines are equivalent to: TKey d = keys[lo + i - 1]; ref TKey keysAtLo = ref Unsafe.Add(ref keys, lo); - ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // No Subtract?? + ref TKey keysAtLoMinus1 = ref Unsafe.Add(ref keysAtLo, -1); // TODO: Use Subtract when available ref TValue valuesAtLoMinus1 = ref Unsafe.Add(ref values, lo - 1); TKey d = Unsafe.Add(ref keysAtLoMinus1, i); TValue dValue = Unsafe.Add(ref valuesAtLoMinus1, i); - var nHalf = n / 2; + int nHalf = n / 2; while (i <= nHalf) { int child = i << 1; - //if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) + // Below lines are equivalent to: if (child < n && comparer(keys[lo + child - 1], keys[lo + child]) < 0) if (child < n && comparer.LessThan(Unsafe.Add(ref keysAtLoMinus1, child), Unsafe.Add(ref keysAtLo, child))) { ++child; } - //if (!(comparer(d, keys[lo + child - 1]) < 0)) + // Below lines are equivalent to: if (!(comparer(d, keys[lo + child - 1]) < 0)) if (!(comparer.LessThan(d, Unsafe.Add(ref keysAtLoMinus1, child)))) - //if (comparer.LessThan(Unsafe.Add(ref keysAtLoMinus1, child), d)) break; - // keys[lo + i - 1] = keys[lo + child - 1] + // Below lines are equivalent to: keys[lo + i - 1] = keys[lo + child - 1] Unsafe.Add(ref keysAtLoMinus1, i) = Unsafe.Add(ref keysAtLoMinus1, child); Unsafe.Add(ref valuesAtLoMinus1, i) = Unsafe.Add(ref valuesAtLoMinus1, child); i = child; } - //keys[lo + i - 1] = d; + // Below lines are equivalent to: keys[lo + i - 1] = d; Unsafe.Add(ref keysAtLoMinus1, i) = d; Unsafe.Add(ref valuesAtLoMinus1, i) = dValue; } @@ -215,9 +214,9 @@ private static void InsertionSort( for (int i = lo; i < hi; ++i) { int j = i; - //t = keys[i + 1]; + var t = Unsafe.Add(ref keys, j + 1); - // TODO: Would be good to be able to update local ref here + if (j >= lo && comparer.LessThan(t, Unsafe.Add(ref keys, j))) { var v = Unsafe.Add(ref values, j + 1); diff --git a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.cs b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.cs index 5d2b4eb586a2..307e68bfb3bc 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.KeysValues.cs @@ -10,10 +10,6 @@ using Internal.Runtime.CompilerServices; #endif -using static System.SpanSortHelpersCommon; -using S = System.SpanSortHelpersKeysValues; -using SDC = System.SpanSortHelpersKeysValues_DirectComparer; - namespace System { internal static partial class SpanSortHelpersKeysValues @@ -29,7 +25,7 @@ internal static void Sort(this Span keys, Span value // PERF: Try specialized here for optimal performance // Code-gen is weird unless used in loop outside - if (!SDC.TrySortSpecialized( + if (!SpanSortHelpersKeysValues_DirectComparer.TrySortSpecialized( ref MemoryMarshal.GetReference(keys), ref MemoryMarshal.GetReference(values), length)) @@ -106,12 +102,12 @@ internal class SpanSortHelper : ISpanSortHelper { public void Sort(ref TKey keys, ref TValue values, int length) { - S.Sort(ref keys, ref values, length, Comparer.Default); + SpanSortHelpersKeysValues.Sort(ref keys, ref values, length, Comparer.Default); } public void Sort(ref TKey keys, ref TValue values, int length, Comparison comparison) { - S.Sort(ref keys, ref values, length, comparison); + SpanSortHelpersKeysValues.Sort(ref keys, ref values, length, comparison); } } @@ -121,14 +117,12 @@ internal class ComparableSpanSortHelper { public void Sort(ref TKey keys, ref TValue values, int length) { - S.Sort(ref keys, ref values, length); + SpanSortHelpersKeysValues.Sort(ref keys, ref values, length); } public void Sort(ref TKey keys, ref TValue values, int length, Comparison comparison) { - // TODO: Check if comparison is Comparer.Default.Compare - - S.Sort(ref keys, ref values, length, comparison); + SpanSortHelpersKeysValues.Sort(ref keys, ref values, length, comparison); } } @@ -168,31 +162,14 @@ internal class SpanSortHelper : ISpanSortHelper) && comparer == null) { - S.Sort(ref keys, ref values, length, Comparer.Default); + SpanSortHelpersKeysValues.Sort(ref keys, ref values, length, Comparer.Default); } else { - S.Sort(ref keys, ref values, length, comparer); + SpanSortHelpersKeysValues.Sort(ref keys, ref values, length, comparer); } - //} - //catch (IndexOutOfRangeException e) - //{ - // throw e; - // //IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); - //} - //catch (Exception e) - //{ - // throw e; - // //throw new InvalidOperationException(SR.InvalidOperation_IComparerFailed, e); - //} } } @@ -204,39 +181,22 @@ internal class ComparableSpanSortHelper public void Sort(ref TKey keys, ref TValue values, int length, TComparer comparer) { - // Add a try block here to detect IComparers (or their - // underlying IComparables, etc) that are bogus. - // - // TODO: Do we need the try/catch? - //try - //{ if (comparer == null || // Cache this in generic traits helper class perhaps (!typeof(TComparer).IsValueType && object.ReferenceEquals(comparer, Comparer.Default))) // Or "=="? { - if (!SDC.TrySortSpecialized(ref keys, ref values, length)) + if (!SpanSortHelpersKeysValues_DirectComparer.TrySortSpecialized(ref keys, ref values, length)) { // NOTE: For Bogus Comparable the exception message will be different, when using Comparer.Default // Since the exception message is thrown internally without knowledge of the comparer - S.Sort(ref keys, ref values, length); + SpanSortHelpersKeysValues.Sort(ref keys, ref values, length); } } else { - S.Sort(ref keys, ref values, length, comparer); + SpanSortHelpersKeysValues.Sort(ref keys, ref values, length, comparer); } - //} - //catch (IndexOutOfRangeException e) - //{ - // throw e; - // //IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer); - //} - //catch (Exception e) - //{ - // throw e; - // //throw new InvalidOperationException(SR.InvalidOperation_IComparerFailed, e); - //} } } } From bb844ac10235bcd0de81b495d6766fc11d6aefe5 Mon Sep 17 00:00:00 2001 From: nietras Date: Sun, 18 Mar 2018 13:15:21 +0100 Subject: [PATCH 6/8] Update MemoryExtensions --- .../shared/System/MemoryExtensions.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/mscorlib/shared/System/MemoryExtensions.cs b/src/mscorlib/shared/System/MemoryExtensions.cs index 008f5def32da..9747443dae02 100644 --- a/src/mscorlib/shared/System/MemoryExtensions.cs +++ b/src/mscorlib/shared/System/MemoryExtensions.cs @@ -1315,10 +1315,7 @@ public static int BinarySearch( /// One or more elements do not implement the interface. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Sort(this Span span) - { - SpanSortHelpersKeys.Sort(span); - } + public static void Sort(this Span span) => SpanSortHelpersKeys.Sort(span); /// /// Sorts the elements in the entire @@ -1326,10 +1323,8 @@ public static void Sort(this Span span) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Sort(this Span span, TComparer comparer) - where TComparer : IComparer - { + where TComparer : IComparer => SpanSortHelpersKeys.Sort(span, comparer); - } /// /// Sorts the elements in the entire @@ -1353,10 +1348,8 @@ public static void Sort(this Span span, Comparison comparison) /// element of the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Sort(this Span keys, Span items) - { + public static void Sort(this Span keys, Span items) => SpanSortHelpersKeysValues.Sort(keys, items); - } /// /// Sorts a pair of spans @@ -1367,11 +1360,9 @@ public static void Sort(this Span keys, Span items) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Sort(this Span keys, - Span items, TComparer comparer) - where TComparer : IComparer - { + Span items, TComparer comparer) + where TComparer : IComparer => SpanSortHelpersKeysValues.Sort(keys, items, comparer); - } /// /// Sorts a pair of spans From 9f420a94537eae9b89867f97aa171bc85aa3a9bd Mon Sep 17 00:00:00 2001 From: nietras Date: Sun, 18 Mar 2018 19:54:33 +0100 Subject: [PATCH 7/8] Remove AggressiveInlining attributes --- .../shared/System/SpanSortHelpers.Common.cs | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Common.cs b/src/mscorlib/shared/System/SpanSortHelpers.Common.cs index 0abe392eba2f..958dd0139233 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.Common.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.Common.cs @@ -56,72 +56,52 @@ internal interface IDirectComparer // internal struct SByteDirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(sbyte x, sbyte y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(sbyte x, sbyte y) => x < y; } internal struct ByteDirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(byte x, byte y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(byte x, byte y) => x < y; } internal struct Int16DirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(short x, short y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(short x, short y) => x < y; } internal struct UInt16DirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(ushort x, ushort y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(ushort x, ushort y) => x < y; } internal struct Int32DirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(int x, int y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(int x, int y) => x < y; } internal struct UInt32DirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(uint x, uint y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(uint x, uint y) => x < y; } internal struct Int64DirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(long x, long y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(long x, long y) => x < y; } internal struct UInt64DirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(ulong x, ulong y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(ulong x, ulong y) => x < y; } internal struct SingleDirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(float x, float y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(float x, float y) => x < y; } internal struct DoubleDirectComparer : IDirectComparer { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool GreaterThan(double x, double y) => x > y; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool LessThan(double x, double y) => x < y; } @@ -131,12 +111,10 @@ internal interface IIsNaN } internal struct SingleIsNaN : IIsNaN { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNaN(float value) => float.IsNaN(value); } internal struct DoubleIsNaN : IIsNaN { - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNaN(double value) => double.IsNaN(value); } } From a7f6ddc6be47b40ef83d12d13c9ef39204aaee43 Mon Sep 17 00:00:00 2001 From: nietras Date: Mon, 19 Mar 2018 21:48:19 +0100 Subject: [PATCH 8/8] Add license to Common --- src/mscorlib/shared/System/SpanSortHelpers.Common.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mscorlib/shared/System/SpanSortHelpers.Common.cs b/src/mscorlib/shared/System/SpanSortHelpers.Common.cs index 958dd0139233..21518958d50c 100644 --- a/src/mscorlib/shared/System/SpanSortHelpers.Common.cs +++ b/src/mscorlib/shared/System/SpanSortHelpers.Common.cs @@ -1,4 +1,8 @@ -using System.Diagnostics; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; using System.Runtime.CompilerServices; #if !netstandard