• Array.Sort()实现细节和效率


    Array.Sort有N个重载,都是调用同一个方法实现排序(Array.Sort<T>先不管)

    public static void Sort(Array keys, Array items, int index, int length, IComparer comparer)
    {
        // 省略一堆参数检查代码
        if ((length > 1) && (((comparer != Comparer.Default) && (comparer != null)) || !TrySZSort(keys, items, index, (index + length) - 1)))
        {
            object[] objArray = keys as object[];
            object[] objArray2 = null;
            if (objArray != null)
            {
                objArray2 = items as object[];
            }
            if ((objArray != null) && ((items == null) || (objArray2 != null)))
            {
                new SorterObjectArray(objArray, objArray2, comparer).QuickSort(index, (index + length) - 1);
            }
            else
            {
                new SorterGenericArray(keys, items, comparer).QuickSort(index, (index + length) - 1);
            }
        }
    }

    其中跟排序有关的代码有三处SorterObjectArray和SorterGenericArray都是Array的内部类,还有个是TrySZSort方法。

    TrySZSort方法从签名来看应该是调用外部非托管的代码。

    [MethodImpl(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
    private static extern bool TrySZSort(Array keys, Array items, int left, int right);

    也就是说,当没有自定义的Comparer对象时,Array优先尝试调用非托管代码对数组进行排序。

    接下来看看SorterGenericArray和SorterObjectArray的唯一区别:
    SorterObjectArray中直接使用Array.GetValue()/Array.SetValue()对数组进行读写;
    SorterGenericArray中则通过Array[]索引器对数组进行读写。

    Array[]索引器是怎样的呢,先看看Array里索引器的实现代码:

    object IList.this[int index]
    {
        get
        {
            return this.GetValue(index);
        }
        set
        {
            this.SetValue(value, index);
        }
    }

    Array索引器都是通过Array.GetValue()/Array.SetValue()对数组进行读写的,为什么SorterGenericArray还多此一举呢?

    应当注意的是,这里的索引器是显式IList接口的索引器的实现方法,并不是Array[]索引器的实现方法,对于泛型,情况则不一样:

    IList<T>泛型接口的定义如下:

    public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
    {
        // ...
        T this[int index] { get; set; }
    }

    IList<T>并不实现IList接口,而是自定义了一个强类型的索引器。

    再来看看List<T>泛型类的定义,List实现了List和IList<T>两种接口:
    public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable

    List<T>索引器的实现代码:

    object IList.this[int index]
    {
        get
        {
            return this[index];
        }
        set
        {
            List<T>.VerifyValueType(value);
            this[index] = (T) value;
        }
    }
    public T this[int index]
    {
        get
        {
            if (index >= this._size)
            {
                ThrowHelper.ThrowArgumentOutOfRangeException();
            }
            return this._items[index];
        }
        set
        {
            if (index >= this._size)
            {
                ThrowHelper.ThrowArgumentOutOfRangeException();
            }
            this._items[index] = value;
            this._version++;
        }
    }

    注意List<T>的代码在实现IList的索引器时是用显示接口声明的,但IList<T>则没有显式写明接口,于是:

    List<int> list = new List<int>(10);
    list[0]; //这里调用的是IList<int>.this[int]索引器,返回的是int类型
    ((IList)list)[0]; //这里调用的是IList.this[int]索引器,返回的是object类型

    值得注意的是Array.Sort()接受的是一个Array对象

    List<int> myList = new List<int>();
    Array.Sort(myList); // 编译错误:参数类型不匹配
    Array.Sort<int>(myList); // 编译错误:注意Array.Sort<T>接受参数的是T[]数组

    其实对于泛型,使用myList.Sort()就可以了(List<T>.Sort<T>实际还是调用了Array.Sort<T>方法,把内部管理的强类型数组传了过去)

    现在回来看Array.Sort方法,既然Array只接受Array对象,为什么还要弄一个SorterGenericArray出来呢?
    我们可以注意到只有当keys或者items(item为null时除外)不能转换成一个数组时(items),才会使用SorterGenericArray.QuickSort进行排序

    有时候会给泛型类型增加一个显示/隐式类型转换,把一个泛型类型转化成一个Array数组(并不是调用ToArray哟),在这种情况下,还是可以调用Array.Sort方法的,如果这时候把这种数组交给SorterObjectArray排序,就可能有两个问题:一是泛型的强类型无法保证,二是IComparer.CompareTo调用时也会有类型问题,所以这里引入了SorterGenericArray类专门处理这类情况,毫无疑问,多了一重类型检查,排序效率相对会低点,可以在下面测试中看出来。于是有了下面的对比测试:

    A.把SorterGenericArray代码提取出来,去除所有try等异常检查,作为MySorterWithComparer类主要代码进行排序
    B.将A的所有GetValue/SetValue直接使用强类型索引器,并使用<和>操作符代替所有Comparer,作为MySorterWithIntArray类主要代码进行排序
    C.Array.Sort()
    D.Array.Sort<int>()
    E.List<int>.Sort<int>()排序

    对长度分别为100/500/1000/10000/10000的int型数组进行排序,发现以下情况:
    1. 无论任何情况,使用Comparer和Array.GetValue()/Array.SetValue()效率都很低
    2. Array.Sort()/Array.Sort<int>()/List<int>.Sort<int>()性能十分接近
    3. 数组长度较小的情况下直接调用Array.Sort的效率比MySorterWithIntArray代码高很多
    4. 数组长度超过1000后,MySorterWithIntArray排序比直接调用Array.Sort()效率高一点点

    上面现象说明:
    1. SorterGenericArray.QuickSort的实现是比较低效的
    2. 或许是因为Array是定义在mscorlib中的,MySorterWithIntArray的代码则是在自己生成的程序集中,排序时使用了mscorlib程序集的类和方法。在数组长度较小的情况下,可能由于跨程序集调用的开销比实际排序的开销要大,所以没有直接调用Array.Sort高效。另外一个可能的原因是Array.Sort调用了非托管的代码。
    3. 数组长度达到一定规模后,跨程序集调用的开销跟实际排序的开销比已经微乎其微,这时频繁的异常检查(Array.Sort内部有个try...catch)的开销逐渐显现。

    其实就算数组比较大(>10w),MySorterWithIntArray花费的时间也只是比Array.Sort快了不到10%,总体上来说,使用Array.Sort还是一种高效方便的排序方法。

    回想现在公司的笔试题,还考啥老掉牙的冒泡排序,事实就是.Net自带的快速排序函数已经非常高效,.Net程序员考排序这种事还是少来吧。

    下面是测试记录(Release+代码优化版本,数据很接近,这里不重复了):
    Array Size: 100
    Measure Name: MySorterWithComparer.QuickSort
    Time Elapsed: 3ms
    CPU Cycles: 4,662,548
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: MySorterWithIntArray.QuickSort
    Time Elapsed: 1ms
    CPU Cycles: 1,856,063
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort
    Time Elapsed: 0ms
    CPU Cycles: 29,953
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort<int>
    Time Elapsed: 0ms
    CPU Cycles: 29,084
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: List.Sort
    Time Elapsed: 0ms
    CPU Cycles: 24,893
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Array Size: 500
    Measure Name: MySorterWithComparer.QuickSort
    Time Elapsed: 3ms
    CPU Cycles: 6,358,979
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: MySorterWithIntArray.QuickSort
    Time Elapsed: 0ms
    CPU Cycles: 103,741
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort
    Time Elapsed: 0ms
    CPU Cycles: 119,614
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort<int>
    Time Elapsed: 0ms
    CPU Cycles: 122,639
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: List.Sort
    Time Elapsed: 0ms
    CPU Cycles: 118,514
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Array Size: 1000
    Measure Name: MySorterWithComparer.QuickSort
    Time Elapsed: 8ms
    CPU Cycles: 14,291,893
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: MySorterWithIntArray.QuickSort
    Time Elapsed: 0ms
    CPU Cycles: 201,663
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort
    Time Elapsed: 0ms
    CPU Cycles: 251,647
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort<int>
    Time Elapsed: 0ms
    CPU Cycles: 248,611
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: List.Sort
    Time Elapsed: 0ms
    CPU Cycles: 236,148
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Array Size: 10000
    Measure Name: MySorterWithComparer.QuickSort
    Time Elapsed: 108ms
    CPU Cycles: 180,057,306
    Gen 0:   3
    Gen 1:   0
    Gen 2:   0

    Measure Name: MySorterWithIntArray.QuickSort
    Time Elapsed: 1ms
    CPU Cycles: 2,456,267
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort
    Time Elapsed: 1ms
    CPU Cycles: 2,663,441
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort<int>
    Time Elapsed: 1ms
    CPU Cycles: 2,671,900
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: List.Sort
    Time Elapsed: 1ms
    CPU Cycles: 2,683,780
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Array Size: 100000
    Measure Name: MySorterWithComparer.QuickSort
    Time Elapsed: 1,224ms
    CPU Cycles: 2,174,465,964
    Gen 0:   39
    Gen 1:   0
    Gen 2:   0

    Measure Name: MySorterWithIntArray.QuickSort
    Time Elapsed: 15ms
    CPU Cycles: 28,609,746
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort
    Time Elapsed: 17ms
    CPU Cycles: 30,748,663
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: Array.Sort<int>
    Time Elapsed: 17ms
    CPU Cycles: 30,883,336
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    Measure Name: List.Sort
    Time Elapsed: 17ms
    CPU Cycles: 30,843,142
    Gen 0:   0
    Gen 1:   0
    Gen 2:   0

    测试代码:

    namespace Just4Test.SortTest
    {
        class SortTestCode
        {
            static void Main(string[] args)
            {
                TestSort(100);
                TestSort(500);
                TestSort(1000);
                TestSort(10000);
                TestSort(100000);
            }
            static int[] GenerateArray(int count)
            {
                Random rnd = new Random();
                int[] array = new int[count];
                for (int i = 0; i < count; i++)
                {
                    array[i] = rnd.Next(int.MinValue, int.MaxValue);
                }
                return array;
            }
            static void TestSort(int count)
            {
                Console.WriteLine("Array Size: " + count.ToString());
                int[] src = GenerateArray(count);
                MySorterWithComparer arr1 = new MySorterWithComparer((int[])(src.Clone()));
                MySorterWithIntArray arr2 = new MySorterWithIntArray((int[])(src.Clone()));
                int[] arr3 = (int[])(src.Clone());
                int[] arr4 = (int[])(src.Clone());
                List<int> list = new List<int>((int[])(src.Clone()));
                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                CodeTimer.BeginMeasure("MySorterWithComparer.QuickSort");
                arr1.QuickSort(0, count - 1);
                CodeTimer.EndMeasure();
                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                CodeTimer.BeginMeasure("MySorterWithIntArray.QuickSort");
                arr2.QuickSort(0, count - 1);
                CodeTimer.EndMeasure();
                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                CodeTimer.BeginMeasure("Array.Sort");
                Array.Sort(arr3);
                CodeTimer.EndMeasure();
                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                CodeTimer.BeginMeasure("Array.Sort<int>");
                Array.Sort<int>(arr4);
                CodeTimer.EndMeasure();
                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                CodeTimer.BeginMeasure("List.Sort");
                list.Sort();
                CodeTimer.EndMeasure();
                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            }
        }
        class MySorterWithComparer
        {
            private Array array;
            private IComparer comparer;
            internal MySorterWithComparer(Array array)
            {
                this.array = array;
                this.comparer = Comparer.Default;
            }
            internal void SwapIfGreaterWithItems(int a, int b)
            {
                if (a != b)
                {
                    if (this.comparer.Compare(this.array.GetValue(a), this.array.GetValue(b)) > 0)
                    {
                        object obj2 = this.array.GetValue(a);
                        this.array.SetValue(this.array.GetValue(b), a);
                        this.array.SetValue(obj2, b);
                    }
                }
            }
            internal void QuickSort(int left, int right)
            {
                do
                {
                    int low = left;
                    int hi = right;
                    int median = (low + ((hi - low) >> 1));
                    this.SwapIfGreaterWithItems(low, median);
                    this.SwapIfGreaterWithItems(low, hi);
                    this.SwapIfGreaterWithItems(median, hi);
                    object y = this.array.GetValue(median);
                    do
                    {
                        while (this.comparer.Compare(this.array.GetValue(low), y) < 0)
                        {
                            low++;
                        }
                        while (this.comparer.Compare(y, this.array.GetValue(hi)) < 0)
                        {
                            hi--;
                        }
                        if (low > hi)
                        {
                            break;
                        }
                        if (low < hi)
                        {
                            object obj3 = this.array.GetValue(low);
                            this.array.SetValue(this.array.GetValue(hi), low);
                            this.array.SetValue(obj3, hi);
                        }
                        if (low != 0x7fffffff)
                        {
                            low++;
                        }
                        if (hi != -2147483648)
                        {
                            hi--;
                        }
                    }
                    while (low <= hi);
                    if ((hi - left) <= (right - low))
                    {
                        if (left < hi)
                        {
                            this.QuickSort(left, hi);
                        }
                        left = low;
                    }
                    else
                    {
                        if (low < right)
                        {
                            this.QuickSort(low, right);
                        }
                        right = hi;
                    }
                }
                while (left < right);
            }
        }
    class MySorterWithIntArray
        {
            private int[] array;
            internal MySorterWithIntArray(int[] array)
            {
                this.array = array;
            }
            internal void SwapIfGreaterWithItems(int a, int b)
            {
                if (a != b && array[a] > array[b])
                {
                    int c = array[a];
                    array[a] = array[b];
                    array[b] = c;
                }
            }
            internal void QuickSort(int left, int right)
            {
                do
                {
                    int low = left;
                    int high = right;
                    int median = (low + ((high - low) >> 1));
                    SwapIfGreaterWithItems(low, median);
                    SwapIfGreaterWithItems(low, high);
                    SwapIfGreaterWithItems(median, high);
                    int y = array[median];
                    do
                    {
                        while (array[low] < y) low++;
                        while (y < array[high]) high--;
                        if (low > high) break;
                        if (low < high)
                        {
                            int tmp = array[low];
                            array[low] = array[low];
                            array[high] = tmp;
                        }
                        low++;
                        high--;
                    }
                    while (low <= high);
                    if ((high - left) <= (right - low))
                    {
                        if (left < high) QuickSort(left, high);
                        left = low;
                    }
                    else
                    {
                        if (low < right) QuickSort(low, right);
                        right = high;
                    }
                }
                while (left < right);
            }
        }
    }
  • 相关阅读:
    C面试复习笔记
    Java面试复习笔记
    Jdk1.6 HTTPS访问问题解决办法
    百度地图轨迹回放,自定义路书,边走边画线
    简单的代码生成小工具(支持模板)
    card布局解决复杂操作的布局问题
    tabpanel如何隐藏页签表头以及基本用法总结
    ExtJS4.2下将表单元素放在菜单时不能进行拷贝的问题解决办法
    照片元数据信息以及在照片中写入gps信息
    带名称空间的xml数据查询
  • 原文地址:https://www.cnblogs.com/neutra/p/1770246.html
Copyright © 2020-2023  润新知