. Net sort array Sort implementation example

Time:2022-6-15

System. Array. Sort<t> yes Net built-in sorting method is flexible and efficient. Everyone has learned some sorting algorithms, such as bubble sort, insert sort, heap sort, etc. but do you know what sort algorithm is used behind this method?

Let’s start with the results. In fact, array Sort uses more than one sort algorithm. In order to ensure high performance in sorting scenarios with different data volumes, the implementation includes insert sort, heap sort and quick sort. Next, see what it has done through the source code.

Array.Sort

https://source.dot.net/#System.Private.CoreLib/Array.cs,ec5718fae85b7640


public static void Sort<T>(T[] array)
{
    if (array == null)
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);

    if (array.Length > 1)
    {
        var span = new Span<T>(ref MemoryMarshal.GetArrayDataReference(array), array.Length);
        ArraySortHelper<T>.Default.Sort(span, null);
    }
}

Here, we sort the int array. First, take a look at the sort method. When the array length is greater than 1, it will first convert the array into a span list, and then call the sort method of the default object of the internal arraysorthelper.

ArraySortHelper


[TypeDependency("System.Collections.Generic.GenericArraySortHelper`1")]
internal sealed partial class ArraySortHelper<T>
    : IArraySortHelper<T>
{
    private static readonly IArraySortHelper<T> s_defaultArraySortHelper = CreateArraySortHelper();

    public static IArraySortHelper<T> Default => s_defaultArraySortHelper;

    [DynamicDependency("#ctor", typeof(GenericArraySortHelper<>))]
    private static IArraySortHelper<T> CreateArraySortHelper()
    {
        IArraySortHelper<T> defaultArraySortHelper;

        if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
        {
            defaultArraySortHelper = (IArraySortHelper<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericArraySortHelper<string>), (RuntimeType)typeof(T));
        }
        else
        {
            defaultArraySortHelper = new ArraySortHelper<T>();
        }
        return defaultArraySortHelper;
    }
}

Default will create different arraysorthelpers according to whether the icomparable<t> interface is implemented. Because I sort the int array above, I call the sort method of genericarraysorthelper.

GenericArraySortHelper

https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,280


internal sealed partial class GenericArraySortHelper<T>
        where T : IComparable<T>
    {
    // Do not add a constructor to this class because ArraySortHelper<T>.CreateSortHelper will not execute it

    #region IArraySortHelper<T> Members

    public void Sort(Span<T> keys, IComparer<T>? comparer)
    {
        try
        {
            if (comparer == null || comparer == Comparer<T>.Default)
            {
                if (keys.Length > 1)
                {
                    // For floating-point, do a pre-pass to move all NaNs to the beginning
                    // so that we can do an optimized comparison as part of the actual sort
                    // on the remainder of the values.
                    if (typeof(T) == typeof(double) ||
                        typeof(T) == typeof(float) ||
                        typeof(T) == typeof(Half))
                    {
                        int nanLeft = SortUtils.MoveNansToFront(keys, default(Span<byte>));
                        if (nanLeft == keys.Length)
                        {
                            return;
                        }
                        keys = keys.Slice(nanLeft);
                    }

                    IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1));
                }
            }
            else
            {
                ArraySortHelper<T>.IntrospectiveSort(keys, comparer.Compare);
            }
        }
        catch (IndexOutOfRangeException)
        {
            ThrowHelper.ThrowArgumentException_BadComparer(comparer);
        }
        catch (Exception e)
        {
            ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_IComparerFailed, e);
        }
    }

First, we will judge whether the sort type is a floating-point type. If so, we will adjust and optimize the sort. Then we call the introsort method and pass in two parameters. The first key is the span list of the array. What is the second? It is a depthlimit parameter of type int. here, the simple understanding is to calculate the depth of the array, because the subsequent recursive operation will be carried out according to this value, and then enter the introsort method.

IntroSort

This method is much clearer. This is array Sort<t> main contents of sorting, and then look down

https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,404


 private static void IntroSort(Span<T> keys, int depthLimit)
{
    Debug.Assert(!keys.IsEmpty);
    Debug.Assert(depthLimit >= 0);

    int partitionSize = keys.Length;
    while (partitionSize > 1)
    {
        if (partitionSize <= Array.IntrosortSizeThreshold)
        {
            if (partitionSize == 2)
            {
                SwapIfGreater(ref keys[0], ref keys[1]);
                return;
            }

            if (partitionSize == 3)
            {
                ref T hiRef = ref keys[2];
                ref T him1Ref = ref keys[1];
                ref T loRef = ref keys[0];

                SwapIfGreater(ref loRef, ref him1Ref);
                SwapIfGreater(ref loRef, ref hiRef);
                SwapIfGreater(ref him1Ref, ref hiRef);
                return;
            }

            InsertionSort(keys.Slice(0, partitionSize));
            return;
        }

        if (depthLimit == 0)
        {
            HeapSort(keys.Slice(0, partitionSize));
            return;
        }
        depthLimit--;

        int p = PickPivotAndPartition(keys.Slice(0, partitionSize));

        // Note we've already partitioned around the pivot and do not have to move the pivot again.
        IntroSort(keys[(p+1)..partitionSize], depthLimit);
        partitionSize = p;
    }
}

When entering the method for the first time, partitionsize is the length of the array. Here is a judgment condition, as follows. Introportsizethreshold is a constant with a value of 16, which is a threshold. If the length of the array is less than or equal to 16, then insertionsort is used. Why 16? It is learned from the comments here that from the perspective of experience, the efficiency of using insert sorting for array lengths of 16 and below is relatively high.


if (partitionSize <= Array.IntrosortSizeThreshold)
{
    if (partitionSize == 2)
    {
        SwapIfGreater(ref keys[0], ref keys[1]);
        return;
    }

    if (partitionSize == 3)
    {
        ref T hiRef = ref keys[2];
        ref T him1Ref = ref keys[1];
        ref T loRef = ref keys[0];

        SwapIfGreater(ref loRef, ref him1Ref);
        SwapIfGreater(ref loRef, ref hiRef);
        SwapIfGreater(ref him1Ref, ref hiRef);
        return;
    }

    InsertionSort(keys.Slice(0, partitionSize));
    return;
}

InsertionSort

If the length of the array is less than or equal to 3, the comparison and exchange are performed directly. If the length is about 3 and less than or equal to 16, the insertionsort is used. The method is as follows:

https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,537


private static void InsertionSort(Span<T> keys)
{
    for (int i = 0; i < keys.Length - 1; i++)
    {
        T t = Unsafe.Add(ref MemoryMarshal.GetReference(keys), i + 1);

        int j = i;
        while (j >= 0 && (t == null || LessThan(ref t, ref Unsafe.Add(ref MemoryMarshal.GetReference(keys), j))))
        {
            Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = Unsafe.Add(ref MemoryMarshal.GetReference(keys), j);
            j--;
        }

        Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = t!;
    }
}
HeapSort
if (depthLimit == 0)
{
    HeapSort(keys.Slice(0, partitionSize));
    return;
}
depthLimit--;

Since the next step is recursive operation, each depthlimit will be reduced by 1. When the depth is 0 and sorting has not been completed, HEAPSORT will be used directly. The method is as follows:

https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,990


private static void HeapSort(Span<TKey> keys, Span<TValue> values)
{
    Debug.Assert(!keys.IsEmpty);

    int n = keys.Length;
    for (int i = n >> 1; i >= 1; i--)
    {
        DownHeap(keys, values, i, n);
    }

    for (int i = n; i > 1; i--)
    {
        Swap(keys, values, 0, i - 1);
        DownHeap(keys, values, 1, i - 1);
    }
}

private static void DownHeap(Span<TKey> keys, Span<TValue> values, int i, int n)
{
    TKey d = keys[i - 1];
    TValue dValue = values[i - 1];

    while (i <= n >> 1)
    {
        int child = 2 * i;
        if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child])))
        {
            child++;
        }

        if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1]))
            break;

        keys[i - 1] = keys[child - 1];
        values[i - 1] = values[child - 1];
        i = child;
    }

    keys[i - 1] = d;
    values[i - 1] = dValue;
}
QuickSort
int p = PickPivotAndPartition(keys.Slice(0, partitionSize), values.Slice(0, partitionSize));
 
IntroSort(keys[(p+1)..partitionSize], values[(p+1)..partitionSize], depthLimit);
partitionSize = p;

Another method, pickpivotandpartition, is called here,
Pivot benchmark, partition partition, this is quick sort! It also uses tail recursive quick sorting, which also uses the three digit middle method. The method is as follows

https://source.dot.net/#System.Private.CoreLib/ArraySortHelper.cs,945


private static int PickPivotAndPartition(Span<TKey> keys, Span<TValue> values)
{
    Debug.Assert(keys.Length >= Array.IntrosortSizeThreshold);

    int hi = keys.Length - 1;

    // Compute median-of-three.  But also partition them, since we've done the comparison.
    int middle = hi >> 1;

    // Sort lo, mid and hi appropriately, then pick mid as the pivot.
    SwapIfGreaterWithValues(keys, values, 0, middle);  // swap the low with the mid point
    SwapIfGreaterWithValues(keys, values, 0, hi);   // swap the low with the high
    SwapIfGreaterWithValues(keys, values, middle, hi); // swap the middle with the high

    TKey pivot = keys[middle];
    Swap(keys, values, middle, hi - 1);
    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.

    while (left < right)
    {
        if (pivot == null)
        {
            while (left < (hi - 1) && keys[++left] == null) ;
            while (right > 0 && keys[--right] != null) ;
        }
        else
        {
            while (GreaterThan(ref pivot, ref keys[++left])) ;
            while (LessThan(ref pivot, ref keys[--right])) ;
        }

        if (left >= right)
            break;

        Swap(keys, values, left, right);
    }

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

summary

This article mainly introduces system Array. The internal implementation of sort<t> sorting. It is found that it uses insert sorting, heap sorting and quick sorting. If you are interested, you can take a look at the sorting implementation in Java or golang. I hope it will be useful to you.

This is about Net sort array This is the end of the article on sort<t> implementation analysis, and more relevant Net sort array Sort<t> please search the previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!