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

结果

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

listCopy.Sort(Comparer.Default)需要314ms

运用

 sealed class IntComparer : IComparer { 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); } public int Compare(int x, int y) { return x.CompareTo(y); } :809ms

评论

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

 internal class GenericComparer : Comparer where T : IComparable { 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; } ... } 

显然,这不应该比使用CompareTo IntComparer变体更快。

我没有在ArraySortHelper找到任何相关内容,它似乎是List.Sort的核心。

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

编辑:上面的时间太低了5.9214729782462845StopwatchTimeSpan有不同的“Tick”定义)。 但是,不影响这一点。

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

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

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

它能够使用< operator,单个cpu指令而不是Int32.CompareTo()所需的10个指令,从而获得速度优势。 或者换句话说,IComparable <>。CompareTo被过度指定用于简单排序。

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

因此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; } } 

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

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

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.Default)) || !TrySZSort(array, null, index, (index + length) - 1))) { ArraySortHelper.Default.Sort(array, index, length, comparer); } 

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