Skip to content

Commit 1b22a69

Browse files
committed
Use generics only for value type comparers
1 parent 4bdcb8d commit 1b22a69

File tree

2 files changed

+247
-12
lines changed

2 files changed

+247
-12
lines changed

src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ArraySortHelper.cs

Lines changed: 227 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ internal static void Sort(Span<T> keys, Comparison<T> comparer)
7070
}
7171
}
7272

73+
internal static void Sort<TComparer>(Span<T> keys, TComparer comparer)
74+
where TComparer : IComparer<T>
75+
{
76+
Debug.Assert(comparer != null, "Check the arguments in the caller!");
77+
78+
// Add a try block here to detect bogus comparisons
79+
try
80+
{
81+
IntrospectiveSort(keys, comparer);
82+
}
83+
catch (IndexOutOfRangeException)
84+
{
85+
ThrowHelper.ThrowArgumentException_BadComparer(comparer);
86+
}
87+
catch (Exception e)
88+
{
89+
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_IComparerFailed, e);
90+
}
91+
}
92+
7393
internal static int InternalBinarySearch(T[] array, int index, int length, T value, IComparer<T> comparer)
7494
{
7595
Debug.Assert(array != null, "Check the arguments in the caller!");
@@ -108,6 +128,19 @@ private static void SwapIfGreater(Span<T> keys, Comparison<T> comparer, int i, i
108128
}
109129
}
110130

131+
private static void SwapIfGreater<TComparer>(Span<T> keys, TComparer comparer, int i, int j)
132+
where TComparer : IComparer<T>, allows ref struct
133+
{
134+
Debug.Assert(i != j);
135+
136+
if (comparer.Compare(keys[i], keys[j]) > 0)
137+
{
138+
T key = keys[i];
139+
keys[i] = keys[j];
140+
keys[j] = key;
141+
}
142+
}
143+
111144
[MethodImpl(MethodImplOptions.AggressiveInlining)]
112145
private static void Swap(Span<T> a, int i, int j)
113146
{
@@ -128,6 +161,17 @@ internal static void IntrospectiveSort(Span<T> keys, Comparison<T> comparer)
128161
}
129162
}
130163

164+
internal static void IntrospectiveSort<TComparer>(Span<T> keys, TComparer comparer)
165+
where TComparer : IComparer<T>, allows ref struct
166+
{
167+
Debug.Assert(comparer != null);
168+
169+
if (keys.Length > 1)
170+
{
171+
IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1), comparer);
172+
}
173+
}
174+
131175
// IntroSort is recursive; block it from being inlined into itself as
132176
// this is currenly not profitable.
133177
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -176,6 +220,53 @@ private static void IntroSort(Span<T> keys, int depthLimit, Comparison<T> compar
176220
}
177221
}
178222

223+
[MethodImpl(MethodImplOptions.NoInlining)]
224+
private static void IntroSort<TComparer>(Span<T> keys, int depthLimit, TComparer comparer)
225+
where TComparer : IComparer<T>, allows ref struct
226+
{
227+
Debug.Assert(!keys.IsEmpty);
228+
Debug.Assert(depthLimit >= 0);
229+
Debug.Assert(comparer != null);
230+
231+
int partitionSize = keys.Length;
232+
while (partitionSize > 1)
233+
{
234+
if (partitionSize <= Array.IntrosortSizeThreshold)
235+
{
236+
237+
if (partitionSize == 2)
238+
{
239+
SwapIfGreater(keys, comparer, 0, 1);
240+
return;
241+
}
242+
243+
if (partitionSize == 3)
244+
{
245+
SwapIfGreater(keys, comparer, 0, 1);
246+
SwapIfGreater(keys, comparer, 0, 2);
247+
SwapIfGreater(keys, comparer, 1, 2);
248+
return;
249+
}
250+
251+
InsertionSort(keys.Slice(0, partitionSize), comparer);
252+
return;
253+
}
254+
255+
if (depthLimit == 0)
256+
{
257+
HeapSort(keys.Slice(0, partitionSize), comparer);
258+
return;
259+
}
260+
depthLimit--;
261+
262+
int p = PickPivotAndPartition(keys.Slice(0, partitionSize), comparer);
263+
264+
// Note we've already partitioned around the pivot and do not have to move the pivot again.
265+
IntroSort(keys[(p + 1)..partitionSize], depthLimit, comparer);
266+
partitionSize = p;
267+
}
268+
}
269+
179270
private static int PickPivotAndPartition(Span<T> keys, Comparison<T> comparer)
180271
{
181272
Debug.Assert(keys.Length >= Array.IntrosortSizeThreshold);
@@ -214,6 +305,45 @@ private static int PickPivotAndPartition(Span<T> keys, Comparison<T> comparer)
214305
return left;
215306
}
216307

308+
private static int PickPivotAndPartition<TComparer>(Span<T> keys, TComparer comparer)
309+
where TComparer : IComparer<T>, allows ref struct
310+
{
311+
Debug.Assert(keys.Length >= Array.IntrosortSizeThreshold);
312+
Debug.Assert(comparer != null);
313+
314+
int hi = keys.Length - 1;
315+
316+
// Compute median-of-three. But also partition them, since we've done the comparison.
317+
int middle = hi >> 1;
318+
319+
// Sort lo, mid and hi appropriately, then pick mid as the pivot.
320+
SwapIfGreater(keys, comparer, 0, middle); // swap the low with the mid point
321+
SwapIfGreater(keys, comparer, 0, hi); // swap the low with the high
322+
SwapIfGreater(keys, comparer, middle, hi); // swap the middle with the high
323+
324+
T pivot = keys[middle];
325+
Swap(keys, middle, hi - 1);
326+
int left = 0, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below.
327+
328+
while (left < right)
329+
{
330+
while (comparer.Compare(keys[++left], pivot) < 0) ;
331+
while (comparer.Compare(pivot, keys[--right]) < 0) ;
332+
333+
if (left >= right)
334+
break;
335+
336+
Swap(keys, left, right);
337+
}
338+
339+
// Put pivot in the right location.
340+
if (left != hi - 1)
341+
{
342+
Swap(keys, left, hi - 1);
343+
}
344+
return left;
345+
}
346+
217347
private static void HeapSort(Span<T> keys, Comparison<T> comparer)
218348
{
219349
Debug.Assert(comparer != null);
@@ -232,6 +362,25 @@ private static void HeapSort(Span<T> keys, Comparison<T> comparer)
232362
}
233363
}
234364

365+
private static void HeapSort<TComparer>(Span<T> keys, TComparer comparer)
366+
where TComparer : IComparer<T>, allows ref struct
367+
{
368+
Debug.Assert(comparer != null);
369+
Debug.Assert(!keys.IsEmpty);
370+
371+
int n = keys.Length;
372+
for (int i = n >> 1; i >= 1; i--)
373+
{
374+
DownHeap(keys, i, n, comparer);
375+
}
376+
377+
for (int i = n; i > 1; i--)
378+
{
379+
Swap(keys, 0, i - 1);
380+
DownHeap(keys, 1, i - 1, comparer);
381+
}
382+
}
383+
235384
private static void DownHeap(Span<T> keys, int i, int n, Comparison<T> comparer)
236385
{
237386
Debug.Assert(comparer != null);
@@ -255,6 +404,30 @@ private static void DownHeap(Span<T> keys, int i, int n, Comparison<T> comparer)
255404
keys[i - 1] = d;
256405
}
257406

407+
private static void DownHeap<TComparer>(Span<T> keys, int i, int n, TComparer comparer)
408+
where TComparer : IComparer<T>, allows ref struct
409+
{
410+
Debug.Assert(comparer != null);
411+
412+
T d = keys[i - 1];
413+
while (i <= n >> 1)
414+
{
415+
int child = 2 * i;
416+
if (child < n && comparer.Compare(keys[child - 1], keys[child]) < 0)
417+
{
418+
child++;
419+
}
420+
421+
if (!(comparer.Compare(d, keys[child - 1]) < 0))
422+
break;
423+
424+
keys[i - 1] = keys[child - 1];
425+
i = child;
426+
}
427+
428+
keys[i - 1] = d;
429+
}
430+
258431
private static void InsertionSort(Span<T> keys, Comparison<T> comparer)
259432
{
260433
for (int i = 0; i < keys.Length - 1; i++)
@@ -271,6 +444,24 @@ private static void InsertionSort(Span<T> keys, Comparison<T> comparer)
271444
keys[j + 1] = t;
272445
}
273446
}
447+
448+
private static void InsertionSort<TComparer>(Span<T> keys, TComparer comparer)
449+
where TComparer : IComparer<T>, allows ref struct
450+
{
451+
for (int i = 0; i < keys.Length - 1; i++)
452+
{
453+
T t = keys[i + 1];
454+
455+
int j = i;
456+
while (j >= 0 && comparer.Compare(t, keys[j]) < 0)
457+
{
458+
keys[j + 1] = keys[j];
459+
j--;
460+
}
461+
462+
keys[j + 1] = t;
463+
}
464+
}
274465
}
275466

276467
internal sealed partial class GenericArraySortHelper<T>
@@ -478,7 +669,7 @@ private static unsafe int PickPivotAndPartition(Span<T> keys)
478669
{
479670
if (pivot == null)
480671
{
481-
while (Unsafe.IsAddressLessThan(ref leftRef, ref nextToLastRef) && (leftRef = ref Unsafe.Add(ref leftRef, 1)) == null) ;
672+
while (Unsafe.IsAddressLessThan(ref leftRef, ref nextToLastRef) && (leftRef = ref Unsafe.Add(ref leftRef!, 1)) == null) ;
482673
while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) && (rightRef = ref Unsafe.Add(ref rightRef, -1)) != null) ;
483674
}
484675
else
@@ -492,13 +683,13 @@ private static unsafe int PickPivotAndPartition(Span<T> keys)
492683
break;
493684
}
494685

495-
Swap(ref leftRef, ref rightRef);
686+
Swap(ref leftRef!, ref rightRef!);
496687
}
497688

498689
// Put the pivot in the correct location.
499690
if (!Unsafe.AreSame(ref leftRef, ref nextToLastRef))
500691
{
501-
Swap(ref leftRef, ref nextToLastRef);
692+
Swap(ref leftRef!, ref nextToLastRef);
502693
}
503694

504695
return (int)((nint)Unsafe.ByteOffset(ref zeroRef, ref leftRef) / sizeof(T));
@@ -630,7 +821,27 @@ public void Sort(Span<TKey> keys, Span<TValue> values, IComparer<TKey>? comparer
630821
}
631822
}
632823

633-
private static void SwapIfGreaterWithValues(Span<TKey> keys, Span<TValue> values, IComparer<TKey> comparer, int i, int j)
824+
public static void Sort<TComparer>(Span<TKey> keys, Span<TValue> values, TComparer comparer)
825+
where TComparer : IComparer<TKey>
826+
{
827+
// Add a try block here to detect IComparers (or their
828+
// underlying IComparables, etc) that are bogus.
829+
try
830+
{
831+
IntrospectiveSort(keys, values, comparer);
832+
}
833+
catch (IndexOutOfRangeException)
834+
{
835+
ThrowHelper.ThrowArgumentException_BadComparer(comparer);
836+
}
837+
catch (Exception e)
838+
{
839+
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_IComparerFailed, e);
840+
}
841+
}
842+
843+
private static void SwapIfGreaterWithValues<TComparer>(Span<TKey> keys, Span<TValue> values, TComparer comparer, int i, int j)
844+
where TComparer : IComparer<TKey>, allows ref struct
634845
{
635846
Debug.Assert(comparer != null);
636847
Debug.Assert(0 <= i && i < keys.Length && i < values.Length);
@@ -663,7 +874,8 @@ private static void Swap(Span<TKey> keys, Span<TValue> values, int i, int j)
663874
values[j] = v;
664875
}
665876

666-
internal static void IntrospectiveSort(Span<TKey> keys, Span<TValue> values, IComparer<TKey> comparer)
877+
internal static void IntrospectiveSort<TComparer>(Span<TKey> keys, Span<TValue> values, TComparer comparer)
878+
where TComparer : IComparer<TKey>, allows ref struct
667879
{
668880
Debug.Assert(comparer != null);
669881
Debug.Assert(keys.Length == values.Length);
@@ -674,7 +886,8 @@ internal static void IntrospectiveSort(Span<TKey> keys, Span<TValue> values, ICo
674886
}
675887
}
676888

677-
private static void IntroSort(Span<TKey> keys, Span<TValue> values, int depthLimit, IComparer<TKey> comparer)
889+
private static void IntroSort<TComparer>(Span<TKey> keys, Span<TValue> values, int depthLimit, TComparer comparer)
890+
where TComparer : IComparer<TKey>, allows ref struct
678891
{
679892
Debug.Assert(!keys.IsEmpty);
680893
Debug.Assert(values.Length == keys.Length);
@@ -720,7 +933,8 @@ private static void IntroSort(Span<TKey> keys, Span<TValue> values, int depthLim
720933
}
721934
}
722935

723-
private static int PickPivotAndPartition(Span<TKey> keys, Span<TValue> values, IComparer<TKey> comparer)
936+
private static int PickPivotAndPartition<TComparer>(Span<TKey> keys, Span<TValue> values, TComparer comparer)
937+
where TComparer : IComparer<TKey>, allows ref struct
724938
{
725939
Debug.Assert(keys.Length >= Array.IntrosortSizeThreshold);
726940
Debug.Assert(comparer != null);
@@ -758,7 +972,8 @@ private static int PickPivotAndPartition(Span<TKey> keys, Span<TValue> values, I
758972
return left;
759973
}
760974

761-
private static void HeapSort(Span<TKey> keys, Span<TValue> values, IComparer<TKey> comparer)
975+
private static void HeapSort<TComparer>(Span<TKey> keys, Span<TValue> values, TComparer comparer)
976+
where TComparer : IComparer<TKey>, allows ref struct
762977
{
763978
Debug.Assert(comparer != null);
764979
Debug.Assert(!keys.IsEmpty);
@@ -776,7 +991,8 @@ private static void HeapSort(Span<TKey> keys, Span<TValue> values, IComparer<TKe
776991
}
777992
}
778993

779-
private static void DownHeap(Span<TKey> keys, Span<TValue> values, int i, int n, IComparer<TKey> comparer)
994+
private static void DownHeap<TComparer>(Span<TKey> keys, Span<TValue> values, int i, int n, TComparer comparer)
995+
where TComparer : IComparer<TKey>, allows ref struct
780996
{
781997
Debug.Assert(comparer != null);
782998

@@ -803,7 +1019,8 @@ private static void DownHeap(Span<TKey> keys, Span<TValue> values, int i, int n,
8031019
values[i - 1] = dValue;
8041020
}
8051021

806-
private static void InsertionSort(Span<TKey> keys, Span<TValue> values, IComparer<TKey> comparer)
1022+
private static void InsertionSort<TComparer>(Span<TKey> keys, Span<TValue> values, TComparer comparer)
1023+
where TComparer : IComparer<TKey>, allows ref struct
8071024
{
8081025
Debug.Assert(comparer != null);
8091026

src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4313,7 +4313,16 @@ public static void Sort<T, TComparer>(this Span<T> span, TComparer comparer) whe
43134313
{
43144314
if (span.Length > 1)
43154315
{
4316-
ArraySortHelper<T>.Default.Sort(span, comparer); // value-type comparer will be boxed
4316+
if (typeof(TComparer).IsValueType)
4317+
{
4318+
#pragma warning disable CS8631
4319+
ArraySortHelper<T>.Sort<TComparer>(span, comparer);
4320+
#pragma warning restore CS8631
4321+
}
4322+
else
4323+
{
4324+
ArraySortHelper<T>.Default.Sort(span, comparer);
4325+
}
43174326
}
43184327
}
43194328

@@ -4380,7 +4389,16 @@ public static void Sort<TKey, TValue, TComparer>(this Span<TKey> keys, Span<TVal
43804389

43814390
if (keys.Length > 1)
43824391
{
4383-
ArraySortHelper<TKey, TValue>.Default.Sort(keys, items, comparer); // value-type comparer will be boxed
4392+
if (typeof(TComparer).IsValueType)
4393+
{
4394+
#pragma warning disable CS8631
4395+
ArraySortHelper<TKey, TValue>.Sort<TComparer>(keys, items, comparer);
4396+
#pragma warning restore CS8631
4397+
}
4398+
else
4399+
{
4400+
ArraySortHelper<TKey, TValue>.Default.Sort(keys, items, comparer);
4401+
}
43844402
}
43854403
}
43864404

0 commit comments

Comments
 (0)