Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Improve Array.Sort(T[]) performance #35297

Merged
merged 2 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 37 additions & 16 deletions src/libraries/System.Private.CoreLib/src/System/Array.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand All @@ -26,6 +27,11 @@ public abstract partial class Array : ICloneable, IList, IStructuralComparable,
internal const int MaxArrayLength = 0X7FEFFFFF;
internal const int MaxByteArrayLength = 0x7FFFFFC7;

// 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;

// This ctor exists solely to prevent C# from generating a protected .ctor that violates the surface area.
private protected Array() { }

Expand Down Expand Up @@ -1916,11 +1922,11 @@ private void IntrospectiveSort(int left, int length)

try
{
IntroSort(left, length + left - 1, 2 * IntrospectiveSortUtilities.FloorLog2PlusOne(length));
IntroSort(left, length + left - 1, 2 * (BitOperations.Log2((uint)length) + 1));
}
catch (IndexOutOfRangeException)
{
IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer);
ThrowHelper.ThrowArgumentException_BadComparer(comparer);
}
catch (Exception e)
{
Expand All @@ -1930,20 +1936,22 @@ private void IntrospectiveSort(int left, int length)

private void IntroSort(int lo, int hi, int depthLimit)
{
Debug.Assert(hi > lo);
Debug.Assert(depthLimit >= 0);

while (hi > lo)
{
int partitionSize = hi - lo + 1;
if (partitionSize <= IntrospectiveSortUtilities.IntrosortSizeThreshold)
if (partitionSize <= IntrosortSizeThreshold)
{
if (partitionSize == 1)
{
return;
}
Debug.Assert(partitionSize >= 2);

if (partitionSize == 2)
{
SwapIfGreater(lo, hi);
return;
}

if (partitionSize == 3)
{
SwapIfGreater(lo, hi - 1);
Expand Down Expand Up @@ -1971,8 +1979,11 @@ private void IntroSort(int lo, int hi, int depthLimit)

private int PickPivotAndPartition(int lo, int hi)
{
Debug.Assert(hi - lo >= IntrosortSizeThreshold);

// Compute median-of-three. But also partition them, since we've done the comparison.
int mid = lo + (hi - lo) / 2;

// Sort lo, mid and hi appropriately, then pick mid as the pivot.
SwapIfGreater(lo, mid);
SwapIfGreater(lo, hi);
Expand All @@ -1994,7 +2005,10 @@ private int PickPivotAndPartition(int lo, int hi)
}

// Put pivot in the right location.
Swap(left, hi - 1);
if (left != hi - 1)
{
Swap(left, hi - 1);
}
return left;
}

Expand Down Expand Up @@ -2122,11 +2136,11 @@ private void IntrospectiveSort(int left, int length)

try
{
IntroSort(left, length + left - 1, 2 * IntrospectiveSortUtilities.FloorLog2PlusOne(length));
IntroSort(left, length + left - 1, 2 * (BitOperations.Log2((uint)length) + 1));
}
catch (IndexOutOfRangeException)
{
IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer(comparer);
ThrowHelper.ThrowArgumentException_BadComparer(comparer);
}
catch (Exception e)
{
Expand All @@ -2136,20 +2150,22 @@ private void IntrospectiveSort(int left, int length)

private void IntroSort(int lo, int hi, int depthLimit)
{
Debug.Assert(hi > lo);
Debug.Assert(depthLimit >= 0);

while (hi > lo)
{
int partitionSize = hi - lo + 1;
if (partitionSize <= IntrospectiveSortUtilities.IntrosortSizeThreshold)
if (partitionSize <= IntrosortSizeThreshold)
{
if (partitionSize == 1)
{
return;
}
Debug.Assert(partitionSize >= 2);

if (partitionSize == 2)
{
SwapIfGreater(lo, hi);
return;
}

if (partitionSize == 3)
{
SwapIfGreater(lo, hi - 1);
Expand Down Expand Up @@ -2177,6 +2193,8 @@ private void IntroSort(int lo, int hi, int depthLimit)

private int PickPivotAndPartition(int lo, int hi)
{
Debug.Assert(hi - lo >= IntrosortSizeThreshold);

// Compute median-of-three. But also partition them, since we've done the comparison.
int mid = lo + (hi - lo) / 2;

Expand All @@ -2200,7 +2218,10 @@ private int PickPivotAndPartition(int lo, int hi)
}

// Put pivot in the right location.
Swap(left, hi - 1);
if (left != hi - 1)
{
Swap(left, hi - 1);
}
return left;
}

Expand Down
Loading