为什么List <t> .Sort使用Comparer <int> .Default的速度是等效自定义比较器的两倍多?</int> </t>

时间:2012-07-11 19:27:45

标签: c# performance sorting

结果

使用1000万随机int的列表(每次相同的种子,平均10次重复):

listCopy.Sort(Comparer<int>.Default)需要 314毫秒

使用

sealed class IntComparer : IComparer<int>
{
  public int Compare(int x, int y)
  {
    return x < y ? -1 : (x == y ? 0 : 1);
  }
}

listCopy.Sort(new IntComparer()) 716ms

一些变化:

  • 使用struct IntComparer代替sealed class:771ms
  • 使用public int Compare(int x, int y) { return x.CompareTo(y); }:809ms

评论

Comparer<int>.Default返回GenericComparer<int>。根据dotPeek,我们有:

internal class GenericComparer<T> : Comparer<T> where T : IComparable<T>
{
  public override int Compare(T x, T y)
  {
    if ((object) x != null)
    {
      if ((object) y != null)
        return x.CompareTo(y);
      else
        return 1;
    }
    else
      return (object) y != null ? -1 : 0;
  }

...
}

显然,这不应该比使用IntComparer的{​​{1}}变体更快。

我在CompareTo中找不到任何相关内容,这似乎是ArraySortHelper<T>的核心。

我只能猜测JIT在这里做了一些神奇的特殊套管(通过专门的排序实现替换那些使用List<T>.Sort的排序,它不执行任何Comparer<int>.Default调用,或类似的东西)?

编辑:上面的时间因素过低IComparer<T>.Compare5.9214729782462845Stopwatch的定义与“嘀嗒”不同。但不影响这一点。

3 个答案:

答案 0 :(得分:22)

原因在Reference Source,system / array.cs源代码文件中很容易看到:

   [ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
   public static void Sort<T>(T[] array, int index, int length, System.Collections.Generic.IComparer<T> comparer) {
       // Argument checking code omitted
       //...

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

           ArraySortHelper<T>.Default.Sort(array, index, length, comparer);
       }
   }

<STRIP>标记的注释解释了它,尽管英语已经破了:)默认比较器的代码路径通过TrySZSort(),这是一个在CLR中实现并用C ++编写的函数。您可以从SSCLI20获取其源代码,它在clr / src / vm / comarrayhelpers.cpp中实现。它使用名为ArrayHelpers<T>::QuickSort()的模板类方法。

它能够使用<运算符,单个cpu指令而不是Int32.CompareTo()所需的10个指令,从而获得速度优势。换句话说,IComparable&lt;&gt; .CompareTo被过度指定以进行简单排序。

这是一个微优化,.NET Framework有很多很多。处于依赖链最底层的代码不可避免的命运,微软永远不会假设他们的代码在客户的应用程序中不会对速度至关重要。

答案 1 :(得分:4)

ILSpy反编译:

    public override int Compare(T x, T y)
    {
        if (x != null)
        {
            if (y != null)
            {
                return x.CompareTo(y);
            }
            return 1;
        }
        else
        {
            if (y != null)
            {
                return -1;
            }
            return 0;
        }
    }

对于值类型,空检查将始终评估为true,因此它们将被优化掉;最终结果将是

public override int Compare(T x, T y)
{
    return x.CompareTo(y);
}

答案 2 :(得分:1)

Int32的默认比较器是CompareTo(int,int)方法。您对默认比较器的假设不正确。

  

IComparable接口提供强类型比较   用于排序通用集合对象的成员的方法。因为   这通常不是直接从开发人员代码调用的。代替,   它由List.Sort()和Add。

等方法自动调用

http://msdn.microsoft.com/en-us/library/4d7sx9hd.aspx。提到的IComparable接口定义了CompareTo方法。

所以我们应该期待你的比较器速度大致相同。那么为什么它会变慢呢?如果我们深入研究.Net中的Sort方法,我们最终会到达这一行:

if ((length > 1) && (((comparer != null) && (comparer != Comparer<T>.Default)) || !TrySZSort(array, null, index, (index + length) - 1)))
{
    ArraySortHelper<T>.Default.Sort(array, index, length, comparer);
}

如果比较器等于该类型的默认比较器,则Array Sort将尝试使用内部优化排序方法。您的比较器不是默认的比较器,因此它会跳过优化的排序。