一起谈.NET技术,数组排序方法的性能比较(中):Array.Sort<T> 实现分析

  昨天我们比较了Array.Sort方法与LINQ排序的性能,知道了LINQ排序的性能以较大幅度落后于Array.Sort方法。而对于Array.Sort来说,性能最高的是其中使用Comparer.Default作为比较器的重载方法。在前文的末尾我们做出了推测:由于排序算法已经近乎一个标准了(快速排序),因此从算法角度来说,Array.Sort方法和LINQ排序上不应该有那么大的差距,因此造成两者性能差异的原因,应该是具体实现方式上的问题。

  下载.NET框架的代码

  既然是比较实现的区别,那么阅读代码是很直接的选择。谈到阅读.NET代码,我们往往会使用.NET Reflector将框架的程序集反编译为C#代码——不排除有朋友喜欢观察IL,并认为它们更直接,更底层。不过我倒觉得在绝大部分情况下,IL能看出的东西从C#也能看出而C#无法了解的IL也帮不上忙,因此许多“由IL发现问题”的文章其实只是自己和自己在爽。

  不过,虽然.NET Reflector将程序集反编译成非常优秀的C#代码,但是还是无法恢复之前的不少信息。例如,局部变量名无法得知,这就给理解代码意图造成了困难。再例如,为了可读性我们可能会将一些条件语句分开写,而C#编译器则可能把它们连做一块儿。至于注释等明显会被去除的东西就更不用说了。因此,在能直接读到代码的情况下,我并不倾向于使用.NET Reflector。

  您可能会说:这不是废话么,不过有些类库——如.NET框架并没有提供源代码,这又有什么办法呢?其实微软也已经公布了.NET框架相当部分程序集的源代码(几乎所有v2.0的程序集,如mscrolib,System,System.Web等等),而且它们其实都可以使用NetMassDownloader直接下载到本地。随员代码发布的还有方便调试的pdb文件,不过这是另一个话题了,我们现在只关心源代码。

  因此,我建议您将所有微软提供的源代码都下载到本地。在看不懂.NET Reflector的反编译结果时,不妨参考一下源代码——还是包含注释的源代码。

  Array.Sort方法实现

  各Array.Sort方法的重载最终都会委托给下面这个重载来执行:

public static void Sort(T[] array, int index, int length, IComparer comparer)
{
    ...
    if (length > 1)
    {
        //
        // TrySZSort is still faster than the generic implementation.
        // The reason is Int32.CompareTo is still expensive than just using "<" or ">".
        //
        if (comparer == null || comparer == Comparer.Default)
        {
            if (TrySZSort(array, null, index, index + length - 1))
            {
                return;
            }
        }
        ArraySortHelper.Default.Sort(array, index, length, comparer);
    }
}

  我们知道,从逻辑上说,Array.Sort方法会使用IComparer类型的比较器来判断两个元素的大小。不过在这里,.NET框架作了一个“特例”,它在用户没有提供比较器,或是直接使用默认比较器的时候利用TrySZSort方法进行排序。如果TrySZSort方法如果返回true,则表示排序完成,直接返回。如果它返回false,则继续使用内置的排序方法进行处理。那么TrySZSort又是如何实现的呢?

private static extern bool TrySZSort(Array keys, Array items, int left, int right);

  这是一个extern方法,说明它是由CLR直接实现的,我们无法得知它的具体算法或是意图。不够从注释中可以得知,这个做法的目的是提高性能(明白注释的优势了吧)。每次使用IComparer的Compare方法进行比较的时候相当于是一次虚方法的调用,CLR需要计算它的偏移量,也无法将其内联。这个细节相对于直接进行int的大小比较来说,也是有较大开销的。使用TrySZSort这种外部方法进行排序,有助于提高在特定情况下的执行效率。

  因此,我们应该可以有足够信心来推断出TrySZSort的作用。TrySZSort方法的作用是对一些可以直接进行比较的原生类型(如int等)进行排序,如果它发现自己无法支持数组中元素的类型,那么就返回false,否则便排序后并返回true。如果TrySZSort返回false,便会使用ArraySortHelper进行排序。而其中的实现便是标准(真的很标准)的快速排序,如果您感兴趣的话可以自行阅读其中的代码。

  值得注意的是,Array是定义在System命名空间下的类型,而ArraySortHelper则定义在System.Collections.Generic命名空间下。在阅读微软提供的源代码时有个麻烦之处便是不容易导航,例如ArraySortHelper类便让我一顿好找。不过,其实我们也可以配合.NET Reflector中强大的导航功能来快速定位到某个类的位置,然后直接去查找它对应的文件。当然,如果您索引了文件内容,也可以查找类似“class ArraySortHelper”这样的关键字,也可以很快找到特定文件。

  Array.Sort其他重载的性能

  以上,我们已经知道为什么使用Comparer.Default作为比较器时性能最高了,因为对于int类型来说,Array.Sort方法会使用最快的TrySZSort方法进行排序。而如果我们使用自定义的Int32Comparer进行比较的话,Compare方法调用的开销是无可避免的,根据实验结果,使用Int32Comparer的执行时间比前者有80%的增加也可以接受。

  那么使用委托进行排序的时候为什么比Int32Comparer更慢一些呢?且看其代码:

public static void Sort(T[] array, Comparison comparison)
{
    ...
    IComparer comparer = new FunctorComparer(comparison);
    Sort(array, comparer);
}

  其实原因很简单,因为.NET框架使用了FunctorComparer封装了comparison委托。这样在每次比较时,它相对于Int32Comparer来说还增加了委托执行的开销——这又相当于一次虚方法的调用:需要寻址,无法内联。

  至此,我们已经分析了Array.Sort各重载的实现方式,也了解了三种Array.Sort重载性能有别的原因。刚好,不久前姜敏兄又回应了我的文章,认为使用Person类,而不是int类型进行比较的时候,仍旧是LINQ排序比较快——由此他认为两种做法的性能和类型也有关系。您认为这个说法正确吗?其实从实现上看,Array.Sort几乎已经是最优的做法了。相反,LINQ排序由于会生成额外的序列,性能想要超过Array.Sort几乎是件不可能的事情。但事实真是如此吗?

  那这测试结果……我也写了一个Person类的测试(http://gist.github.com/282796),还是Array.Sort比较快。那么为什么两个结果会有所不同呢?这是一个值得探讨的问题。

相关文章

  1. 数组排序方法的性能比较(1):注意事项及试验
  2. 数组排序方法的性能比较(2):Array.Sort实现分析
  3. 数组排序方法的性能比较(3):LINQ排序实现分析
时间: 2024-10-25 06:28:04

一起谈.NET技术,数组排序方法的性能比较(中):Array.Sort<T> 实现分析的相关文章

一起谈.NET技术,数组排序方法的性能比较(3):LINQ排序实现分析

上次我们分析了Array.Sort方法的实现方式,并了解到类库会为一些特例而使用高性能的排序方式--int数组便是这样一例,因此从测试结果上来看其性能特别高.不过从数据上看,即便是在普通的情况下,Array.Sort的性能也比LINQ排序要高.不过也有朋友从测试中得出的结论正好相反,这又是为什么呢?那么现在,我们再来分析一下LINQ排序的实现方式吧,希望这样可以了解到两者性能差别的秘密. 只可惜,LINQ排序的代码在System.Core.dll程序集中,微软没有发布这部分源代码,我们只得使用.

数组排序方法的性能比较(上):注意事项及试验

昨天有朋友写了一篇文章,其中比较了List的Sort方法与LINQ中排序方法的性能,而最终得到的结果是"LINQ排序方法性能高于List.Sort方法".这个结果不禁让我很疑惑.因为List.Sort方法是改变容器内部元素的顺序,而LINQ排序后得到的是一个新的序列.假如两个排序方法的算法完全一致,LINQ排序也比对方多出元素复制的开销,为什么性能反而会高?如果LINQ排序的算法/实现更为优秀,那为什么.NET Fx不将List.Sort也一并优化一下呢?于是今天我也对这个问题进行了简

数组排序方法的性能比较(中):Array.Sort 实现分析

昨天我们比较了Array.Sort方法与LINQ排序的性能,知道了LINQ排序的性能以较大幅度落后于Array.Sort方法.而对于Array.Sort来说,性能最高的是其中使用Comparer.Default作为比较器的重载方法.在前文的末尾我们做出了推测:由于排序算法已经近乎一个标准了(快速排序),因此从算法角度来说,Array.Sort方法和LINQ排序上不应该有那么大的差距,因此造成两者性能差异的原因,应该是具体实现方式上的问题. 下载.NET框架的代码 既然是比较实现的区别,那么阅读代

艾伟_转载:数组排序方法的性能比较(上):注意事项及试验

昨天有朋友写了一篇文章,其中比较了List的Sort方法与LINQ中排序方法的性能,而最终得到的结果是"LINQ排序方法性能高于List.Sort方法".这个结果不禁让我很疑惑.因为List.Sort方法是改变容器内部元素的顺序,而LINQ排序后得到的是一个新的序列.假如两个排序方法的算法完全一致,LINQ排序也比对方多出元素复制的开销,为什么性能反而会高?如果LINQ排序的算法/实现更为优秀,那为什么.NET Fx不将List.Sort也一并优化一下呢?于是今天我也对这个问题进行了简

艾伟_转载:数组排序方法的性能比较(中):Array.Sort&lt;T&gt; 实现分析

昨天我们比较了Array.Sort方法与LINQ排序的性能,知道了LINQ排序的性能以较大幅度落后于Array.Sort方法.而对于Array.Sort来说,性能最高的是其中使用Comparer.Default作为比较器的重载方法.在前文的末尾我们做出了推测:由于排序算法已经近乎一个标准了(快速排序),因此从算法角度来说,Array.Sort方法和LINQ排序上不应该有那么大的差距,因此造成两者性能差异的原因,应该是具体实现方式上的问题. 下载.NET框架的代码 既然是比较实现的区别,那么阅读代

数组排序方法的性能比较(2):Array.Sort&amp;lt;T&amp;gt;实现分析

昨天我们比较了Array.Sort<T>方法与LINQ排序的性能,知道了LINQ排序的性能以较大幅度落后 于Array.Sort<T>方法.而对于Array.Sort<T>来说,性能最高的是其中使用 Comparer<int>.Default作为比较器的重载方法.在前文的末尾我们做出了推测:由于排序算法已 经近乎一个标准了(快速排序),因此从算法角度来说,Array.Sort<T>方法和LINQ排序上不应该 有那么大的差距,因此造成两者性能差异的

数组排序方法的性能比较(5):对象大小与排序性能

在我公开测试结果之后,有朋友也进行了其他测试.在测试中我使用的是int数组,经过分析之后我们 了解到Array.Sort<T>对于int数组有特殊的优化.于是,某些朋友使用了一些引用类型的数组进行 排序,得到Array.Sort<T>方法的性能落后于LINQ排序--虽然由于测试方式的问题,这个结果和 结论都不太妥当.不过在讨论的过程中,我们都意识到了一个问题:在其他条件不变的情况下,引用类型 的字段越多,Array.Sort<T>方法所需时间就越久.这次我们就来讨论一下

数组排序方法的性能比较(4):LINQ方式的Array排序

经过前两篇文章的分析,我们已经了解了Array.Sort<T>与LINQ排序两种实现方式的差别:前者 直接比较两个元素的大小,而后者先选出每个元素的"排序依据"再进行比较.因此,虽然后者需要相对 较多的"周边工作",但由于每次比较时都可以仅仅使用高效的基础类型(如int),因此从整体来看, 两者的性能高低难以辨别.不过,既然我们已经了解LINQ排序"高效"的原因,又能否将其利用在数组排 序上呢?程序是人写的,此类问题大都有肯定的答案.

数组排序方法的性能比较(3):LINQ排序实现分析

上次我们分析了Array.Sort<T>方法的实现方式,并了解到类库会为一些特例而使用高性能的排 序方式--int数组便是这样一例,因此从测试结果上来看其性能特别高.不过从数据上看,即便是在普 通的情况下,Array.Sort<T>的性能也比LINQ排序要高.不过也有朋友从测试中得出的结论正好相 反,这又是为什么呢?那么现在,我们再来分析一下LINQ排序的实现方式吧,希望这样可以了解到两者性 能差别的秘密. 只可惜,LINQ排序的代码在System.Core.dll程序集中,微软没