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已经确定了主要的根本原因。我将把这个标记为答案!