C# List.Contains()的循环实现似乎比内置实现快。它是?若然,原因为何?
() 我正在比较使用C# List.Contains()的循环实现似乎比内置实现快。它是?若然,原因为何?,c#,optimization,generic-list,C#,Optimization,Generic List,() 我正在比较使用List.Contains()在列表中查找true值的计时与手动循环的计时 我看到的结果与其他人报告的结果不同。我已经在几个系统上试过了,在我试过的所有系统上,循环速度似乎都快了2到3.5倍。这些系统包括运行XP和.Net 4的5年笔记本电脑,以及运行Windows 8和.Net 4.5的最新电脑 其他人报告了不同的结果,即List.Contains()的速度大约与循环的速度相同,或略快于循环的速度 这是我的测试代码 using System; using System.Co
List.Contains()
在列表中查找true
值的计时与手动循环的计时
我看到的结果与其他人报告的结果不同。我已经在几个系统上试过了,在我试过的所有系统上,循环速度似乎都快了2到3.5倍。这些系统包括运行XP和.Net 4的5年笔记本电脑,以及运行Windows 8和.Net 4.5的最新电脑
其他人报告了不同的结果,即List.Contains()
的速度大约与循环的速度相同,或略快于循环的速度
这是我的测试代码
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main()
{
int size = 10000000;
int count = 10;
List<bool> data = new List<bool>(size);
for (int i = 0; i < size; ++i)
data.Add(false);
var sw = new Stopwatch();
for (int trial = 0; trial < 5; ++trial)
{
sw.Restart();
for (int i = 0; i < count; ++i)
TestViaLoop(data);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds + " TestViaLoop()");
sw.Restart();
for (int i = 0; i < count; ++i)
TestViaListContains(data);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds + " TestViaListContains()");
Console.WriteLine();
}
}
static bool TestViaLoop(List<bool> data)
{
for (int i = 0; i < data.Count; ++i)
if (data[i])
return true;
return false;
}
static bool TestViaListContains(List<bool> data)
{
return data.Contains(true);
}
}
}
正如您所看到的,在我的系统中,循环大约占用1/3的时间
现在,如果我们使用Resharper
查看List.Contains()
的实现,它如下所示:
bool Contains(T item)
{
if (item == null)
{
for (int j = 0x0; j < this._size; j++)
{
if (this._items[j] == null)
{
return true;
}
}
return false;
}
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
for (int i = 0x0; i < this._size; i++)
{
if (comparer.Equals(this._items[i], item))
{
return true;
}
}
return false;
}
public override bool Equals(T x, T y)
{
if ((object) x != null)
{
if ((object) y != null)
return x.Equals(y);
else
return false;
}
else
return (object) y == null;
}
bool包含(T项)
{
如果(项==null)
{
对于(int j=0x0;j
虽然它使用的是Comparer.Equals()
(这会使它比循环慢),但它也直接使用私有\u items[]
数组,这避免了将用于我的循环实现的索引范围检查
我有三个问题:
其他人能复制我看到的结果吗?(请记住在调试器外部运行发布版本。)
如果是这样的话,有人能解释一下我的循环为什么比List.Contains()
快得多吗
如果没有,谁能解释为什么我看到我的循环更快
这不仅仅是我的学术兴趣,因为我编写的代码可以处理大量的数字数据,并且需要尽可能快,这是我需要了解的事情。(注意:是的,我会分析事情,只会尝试优化需要优化的东西……我知道过早优化的问题。)
[编辑]
我觉得这可能与处理器有关。我试过的所有系统都有英特尔处理器,尽管型号非常不同,从3.8GHz的四核到1.6GHz的奔腾M单核
对于那些看到循环运行较慢的人,您是否正在运行英特尔处理器?它使用GenericEqualityComparer,如果我们看一下Equals方法的实现,它是这样的:
bool Contains(T item)
{
if (item == null)
{
for (int j = 0x0; j < this._size; j++)
{
if (this._items[j] == null)
{
return true;
}
}
return false;
}
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
for (int i = 0x0; i < this._size; i++)
{
if (comparer.Equals(this._items[i], item))
{
return true;
}
}
return false;
}
public override bool Equals(T x, T y)
{
if ((object) x != null)
{
if ((object) y != null)
return x.Equals(y);
else
return false;
}
else
return (object) y == null;
}
当它检查对象是否不等于null时,它将对它们进行装箱,并且您将得到两个装箱操作。此IL代码显示了它的外观:
IL_0002: box !T
IL_0007: ldnull
IL_0008: ceq
由280Z28编辑:相同方法的CIL在.NET 4.5中略有不同
public override bool Equals(T x, T y)
{
if (x != null)
return ((y != null) && x.Equals(y));
if (y != null)
return false;
return true;
}
这是IL。对于查看反射器的任何人,请注意brfalse.s
和brnull.s
是相同的指令
L_0000: ldarg.1
L_0001: box !T
L_0006: brnull.s L_0021
...
基线JIT编译器不优化box操作,但我没有与NGen或优化编译器检查它们是否优化。循环实现产生与包含的相同的输出,但不能在一般情况下使用它。也就是说,对于更复杂的对象,您必须使用Equals
比较。Contains
实现比您的实现执行了更多的工作,所以我不明白为什么您希望它在这种情况下更快
如果您有一个自定义Person
对象列表,并且覆盖了Equals
方法来比较,比如说,它们的地址名称
和出生日期
,那么循环将以几乎相同的性能代价执行
我希望使用基本值,那么是的,循环迭代的性能将优于通用的包含
,但这是一个过早的优化,对于更复杂的对象比较,您不会(实质上)比包含
做得更好。我通过循环得到大约185个,通过包含得到365个,所以:是的,我可以重新编程-我不会对差异感到兴奋,尽管。。。如果我想要最好的“contains”,我会使用HashSet
或类似的方法。然而,我不能责备!对于ListContains方法,我得到了大约2:1的支持。第一个示例给出:890 TestViaLoop()450 TestViaListConstans()…这告诉您方法调用非常昂贵(comparer.Equals
)。继续,这是怎么告诉你方法调用是昂贵的?这里还有更多的工作要做…@MarcGravel我同意你的观点,但范围不仅仅是包含的。例如,如果我使用List.FindIndex(pred)
来查找列表中第一个负元素的索引(这是我们在某些计算中碰巧需要做的事情),那么循环速度又快了三倍左右。。。我想这是在做一些完全不同的事情。啊哈!这听起来像是一个令人信服的解释,解释了为什么循环速度更快。然而,这并不能解释为什么有些人认为循环运行得较慢!(如果他们是…现在看起来每个人都看到循环运行得更快。)在我的系统上的测试中,使用GenericEqualityComparer.Equals()方法比布尔值的简单比较(即a==b)慢100%多一点。这只是为了比较,而不是循环/控制结构。我认为@ws0205已经确定了主要的根本原因。我将把这个标记为答案!