繁体   English   中英

为什么是List <T> 。使用Comparer排序 <int> 。默认速度是同等自定义比较器的两倍多?

[英]Why is List<T>.Sort using Comparer<int>.Default more than twice as fast as an equivalent custom comparer?

结果

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

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

运用

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); } 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;
  }

...
}

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

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

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

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

原因很容易在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()的模板类方法。

它能够使用< 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;
        }
    }

对于值类型,空检查将始终评估为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<T>.Default)) || !TrySZSort(array, null, index, (index + length) - 1)))
{
    ArraySortHelper<T>.Default.Sort(array, index, length, comparer);
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM