循环执行List.Contains的()出现快于内置的一个。 是吗? 如果是这样,为什么?(L

2019-08-31 09:37发布

( 这个问题是从这里开始的讨论 )

我比较定时为寻找一个true的值List<bool>使用List.Contains()与那些用于手卷回路。

我看到那些其他人不同的结果。 我已经尝试过了几个系统,并循环似乎快了2和3.5倍之间的所有我已经试过它的系统。 这些系统的范围从与.net 4运行XP运行Windows 8和.NET 4.5最近的PC 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);
        }
    }
}

为了测试此代码,你应该编译它作为一个x86发布版本,并从调试器外部运行它。

下面是使用.NET 4.5框架(虽然我与.NET 4中类似的结果)我的Windows 8的x64 PC我的结果:

Times are in milliseconds

126 TestViaLoop()
441 TestViaListContains()

122 TestViaLoop()
428 TestViaListContains()

131 TestViaLoop()
431 TestViaListContains()

138 TestViaLoop()
426 TestViaListContains()

122 TestViaLoop()
439 TestViaListContains()

正如你可以看到,环路采用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;
}

尽管它是使用Comparer.Equals()其中应使它比环路更慢)它也使用私有_items[]阵列直接,这避免了将要被用于我的循环执行索引范围检查。

我有三个问题:

  1. 别人可以复制我看到的结果吗? (记住要运行调试器之外的发布版本。)
  2. 如果是这样,任何人可以解释我的循环怎么可以这么快于多List.Contains()
  3. 如果没有,任何人都可以解释为什么我看到我的循环要快?

这不仅仅是学术上的兴趣给我的,因为我写的,与大量的数字数据,并且必须尽可能快的代码工作,这是诸如此类的事情,我需要知道。 (注意:是的,我个人资料的事情,只能尽量优化的东西,需要优化......我知道过早优化的问题。)

[编辑]

它发生,我认为这可能是处理器相关的。 所有我已经试过它的系统有英特尔处理器,虽然很不同的型号,从四核3.8GHz的处的奔腾M单核1.6 GHz的...

对于那些你们谁看到了循环运行的速度较慢,你在运行英特尔处理器?

Answer 1:

它采用GenericEqualityComparer,如果我们看一下Equals方法是实施看起来是这样的:

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

当检查对象是否不等于空,这让他们拳,你得到两个装箱操作。 这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.sbrnull.s是相同的指令。

L_0000: ldarg.1 
L_0001: box !T
L_0006: brnull.s L_0021
...

基线JIT编译器不优化掉的暗箱操作,但我还没有NGEN或优化编译器检查,看看如果他们这样做。



Answer 2:

你的循环执行产生相同的输出Contains ,但你不能在一般情况下使用它。 即你将不得不最终使用Equals用于更复杂的对象比较。 该Contains实现比你实现执行更多的工作 ,所以我不明白为什么你应该希望它是在这种情况下更快。

如果你有自定义的列表Person的对象说了,推翻了Equals方法来比较,比如说,他们的Address Name SSNumberDateOfBirth ,该循环将执行在几乎相同的性能开销。

我希望为原始值,然后是一个循环迭代是要超越一般Contains ,但这是一个过早的优化,你不会做的(基本上)优于Contains了更复杂的对象比较。



文章来源: Loop implementation of List.Contains() appears faster than the built-in one. Is it? If so, why?