为什么比包含更慢?

时间:2010-02-05 15:40:03

标签: c# performance

我设计了以下测试:

var arrayLength=5000;
object[] objArray=new object[arrayLength];

for(var x=0;x<arrayLength;x++)
{
    objArray[x]=new object();
}
objArray[4000]=null;
const int TestSize=int.MaxValue;

System.Diagnostics.Stopwatch v= new Stopwatch();
v.Start();
for(var x=0;x<10000;x++)
{
    objArray.Contains(null);
}
v.Stop();
objArray.Contains(null).Dump();
v.Elapsed.ToString().Dump("Contains");

//Any ==
v.Reset();
v.Start();
for(var x=0;x<10000;x++)
{
    objArray.Any(o=>o==null);
}
v.Stop();
objArray.Any(x=>x==null).Dump();
v.Elapsed.ToString().Dump("Any");

//Any Equals
v.Reset();
v.Start();
for(var x=0;x<10000;x++)
{
    objArray.Any(obj=>object.Equals( obj,null));
}
v.Stop();
objArray.Any(obj=>object.Equals( obj,null)).Dump();
v.Elapsed.ToString().Dump("Any");

不存在null时的结果:

  • Contains False 00:00:00.0606484
  • Any == False 00:00:00.7532898
  • Any object.Equals False 00:00:00.8431783

元素4000处存在null时:

  • Contains True 00:00:00.0494515
  • Any == True 00:00:00.5929247
  • Any object.Equals True 00:00:00.6700742

当元素10出现null时:

  • Contains True 00:00:00.0038035
  • Any == True 00:00:00.0025687
  • Any True 00:00:00.0033769

因此,当物体靠近前方时,Any稍快一点;当它在后面时,速度要慢得多。为什么呢?

4 个答案:

答案 0 :(得分:8)

Any必须为它检查的每个元素调用一个委托(一个额外的callvirt指令,它不太可能被JIT内联)。 Contains仅执行该检查。这就是Any速度慢的原因。我怀疑Any看起来比包含元素很早的时候看起来更快的事实是基准测试不能很容易地反映它,因为它们非常接近。方法调用的设置时间是在这种情况下完成的大部分工作(而不是实际的搜索操作)。

The anonymous method:
--- C:\Users\Mehrdad\AppData\Local\Temporary Projects\ConsoleApplication1\Program.cs 
            Console.WriteLine(s.Any(a => a == 1));
00000000  xor         eax,eax 
00000002  cmp         ecx,1 
00000005  sete        al 
00000008  ret 

Relevant part of Enumerable.Any code:
...
00000051  mov         edx,eax 
00000053  mov         rcx,qword ptr [rbx+8] 
00000057  call        qword ptr [rbx+18h]   // calls the anonymous method above
0000005a  movzx       ecx,al 
0000005d  test        ecx,ecx 
...

答案 1 :(得分:4)

任何一个都比较慢,因为Contains是针对您正在使用的特定容器(Array / List / etc)定制的,因此它没有启动IEnumerable的开销,一直调用MoveNext()等等。

但是,使用Any会使重构变得更容易,因为如果你更改了那个集合,你可以继续使用它,所以如果你通过一个分析器知道这是一个重要的代码,我真的只能将它改为Contains。如果是这样,你可能最终会使用更智能的数据结构,就像HashSet一样,因为Any和Contains都是O(n)。

答案 2 :(得分:4)

正如其他人已经注意到的,Contains和Any方法都是Enumerable的扩展方法。性能的巨大差异有几个原因:

首先,您为Any提供委托,必须为每个对象调用,而Contains方法不必。委托调用与调用接口方法的速度一样快。因此,Any更慢。

接下来,其他人似乎错过了一些东西,Contains扩展方法对实现ICollection的集合进行了性能优化。因为object []实现了ICollection,所以扩展方法调用会导致对数组本身进行方法调用。在内部,这个array.Contains方法使用一个简单的for循环迭代数组来比较值。这意味着只需迭代数组就可以完成数组边界检查。

因为Any方法必须调用您的委托,所以不能像使用Contains方法那样进行性能优化。这意味着Any方法使用IEnumerable接口迭代集合,这导致接口调用+数组边界检查+对每个元素的委托调用。将它与array.Contains进行比较,其中没有接口调用,没有委托调用和单个边界检查。

[更新]: 最后一点。使用小集合的Any更快(在你的情况下在集合的开头有一个空值)的原因与Enumerable.Contains执行的ICollection的强制转换有关当你自己执行转换为ICollection时,你'我会看到对Contains的调用比Any:

更快
for(var x=0;x<10000;x++)
{
    ICollection<object> col = objArray;
    col.Contains(null);
}

答案 3 :(得分:2)

我猜它与Any事件有关,这是一个扩展方法,是LINQ库的一部分,涉及使用委托(通过Func&lt;&gt;语法)。任何时候你必须呼唤一个单独的方法(特别是作为一个代表),它会慢下来。