【问题标题】:Why is List<T>.ForEach faster than standard foreach?为什么 List<T>.ForEach 比标准 foreach 快?
【发布时间】:2010-10-19 03:34:01
【问题描述】:

考虑一下:

必要条件:

//The alphabet from a-z
List<char> letterRange = Enumerable.Range('a', 'z' - 'a' + 1)
.Select(i => (Char)i).ToList(); //97 - 122 + 1 = 26 letters/iterations

标准 foreach:

foreach (var range in letterRange)
{
    Console.Write(range + ",");
}
Console.Write("\n");

内置foreach:

letterRange.ForEach(range => Console.Write(range + ",")); //delegate(char range) works as well
Console.Write("\n");

我尝试过将它们相互计时,内置的 foreach 速度提高了 2 倍,这似乎很多。

我用谷歌搜索过,但似乎找不到任何答案。

另外,关于:In .NET, which loop runs faster, 'for' or 'foreach'?

for (int i = 0; i < letterRange.Count; i++)
{
    Console.Write(letterRange[i] + ",");
}
Console.Write("\n");

据我所知,它的执行速度并不比标准 foreach 快。

【问题讨论】:

    标签: c# .net list foreach benchmarking


    【解决方案1】:

    我认为您的基准测试存在缺陷。 Console.Write 是一项 I/O 绑定任务,它是基准测试中最耗时的部分。这是一个微基准,应该非常仔细地完成以获得准确的结果。

    这是一个基准:http://diditwith.net/PermaLink,guid,506c0888-8c5f-40e5-9d39-a09e2ebf3a55.aspx(看起来不错,但我自己还没有验证过)。 截至 2015 年 8 月 14 日,该链接似乎已断开

    【讨论】:

    • 我相信你是对的。你能提供一些合适的基准测试结果吗?
    • 相反,创建一个具有足够初始容量的字符串构建器来保存整个结果并将每个字符串附加到该结果中。然后在你停止计时器之后输出所有内容。
    • 啊,我没有注意到文章还包含 Lists.ForEach 基准。那谢谢
    • 微软根据其一篇文章表示不同意:docs.microsoft.com/en-us/visualstudio/ide/reference/…
    • @ATL_DEV 您能否解释一下您所指的那个页面中究竟是什么暗示了“微软不同意”的答案?
    【解决方案2】:

    当您进入一个 foreach 循环时,您会枚举每个项目。该枚举每次迭代会导致两个方法调用:一个调用IEnumerator&lt;T&gt;.MoveNext(),另一个调用IEnumerator&lt;T&gt;.Current。那是两条call IL 指令。

    List&lt;T&gt;.ForEach 更快,因为它每次迭代只有一个方法调用——无论您提供的Action&lt;T&gt; 委托是什么。 这是一条callvirt IL 指令。这比两条call 指令快得多。

    正如其他人指出的那样,像 Console.WriteLine() 这样的 IO 绑定指令会污染您的基准测试。做一些可以完全限制在记忆中的事情,比如将一个序列的元素加在一起。

    【解决方案3】:

    这是因为 foreach 方法没有使用枚举器。枚举器(foreach)往往比基本的 for 循环慢:

    这是 ForEach 方法的代码:

    public void ForEach(Action<T> action)
    {
        if (action == null)
        {
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
        }
        for (int i = 0; i < this._size; i++)
        {
            action(this._items[i]);
        }
    }
    

    虽然我预计会有差异,但我有点惊讶它与您所说的一样大。使用枚举器方法,您将创建一个额外的对象,然后采取额外的步骤来确保枚举器不会失效(修改集合)。您还通过一个额外的函数调用 Current() 来获取成员。所有这些都增加了时间。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-27
      • 2012-07-14
      • 1970-01-01
      • 2011-05-01
      • 2013-09-23
      • 2016-08-27
      • 2012-03-07
      • 1970-01-01
      相关资源
      最近更新 更多