【问题标题】:Performance loss caused by Linq [duplicate]Linq引起的性能损失[重复]
【发布时间】:2014-07-04 08:58:44
【问题描述】:

最近的一次更新在我的代码中造成了严重的性能损失。通过使用分析器,我发现丢失是由使用 Linq 的单行引起的。我做了一些测试,发现 Linq 比foreach 慢很多。

        List<int> numbers = new List<int>();
        for (int i = 0; i < 1000; ++i)
            numbers.Add(i);
        var stopWatch = new Stopwatch();

        {
            int total = 0;
            for (int i = 0; i < 1000; ++i)
                total += i;
        }
        stopWatch.Start();
        for (int j = 0; j < 1000000; ++j)
        {
            int total = 0;
            for (int i = 0; i < 1000; ++i)
                total += i;
        }
        stopWatch.Stop();
        Console.WriteLine("Benchmark run time: {0}", stopWatch.ElapsedMilliseconds);

        {
            int total = 0;
            foreach (int i in numbers)
                total += i;
        }
        stopWatch.Restart();
        for (int j = 0; j < 1000000; ++j)
        {
            int total = 0;
            foreach (int i in numbers)
                total += i;
        }
        stopWatch.Stop();
        Console.WriteLine("foreach run time: {0}", stopWatch.ElapsedMilliseconds);

        {
            int total = 0;
            total += numbers.Sum();
        }
        stopWatch.Restart();
        for (int j = 0; j < 1000000; ++j)
        {
            int total = 0;
            total += numbers.Sum();
        }
        stopWatch.Stop();
        Console.WriteLine("Sum run time: {0}", stopWatch.ElapsedMilliseconds);

输出:

基准运行时间:653 foreach 运行时间:3862 总运行时间:10233

这是否意味着我们应该始终避免在性能关键部分使用 Linq?

更新: 修复秒表不重置的BUG 在为 JIT 启动秒表之前执行每个测试一次 是的,这是在发布模式,没有调试

【问题讨论】:

  • 你甚至没有重置你的秒表......另外,这甚至是一个发布版本吗?我很难相信它会像我期望的那样完全优化 total 变量。此外,您应该让 JIT 在计时之前完成它的工作(即,调用每个测试一次)。
  • 请注意,系统和 .NET 版本对答案也非常重要。另见referencesource.microsoft.com/System.Core/System/Linq/…
  • 我打算在分析器下运行它,看看为什么 Sum 需要这么长时间,但在我的机器上 Sum 用了 8.767 秒,而 foreach 用了 8.314 秒。在检查算术是差异的假设下,我修改了控制台应用程序以使用检查算术,你瞧,数字是 7.293 秒和 7.917。奇怪的是它变得更快。 Sum 做的另一件事是您的代码没有做的,那就是对集合的 null 检查。
  • 我只是想到了另一个不同之处。您的代码调用列表的公共 GetEnumerator 属性,该属性返回一个值类型。 Sum 方法调用显式 IEnumerable 实现,该实现返回对值类型枚举器的装箱实例的引用。这种额外的间接性可能是造成这种差异的原因。实际上,修改代码以强制转换为 IEnumerable 会有所不同。所以不是 Sum 没有优化,是 List.GetEnumerator() 优化了,但是 Sum 不能利用优化。
  • 好收获。我正在使用 VS 2013。我记得在参考资料中看到一些最初作为迭代器块编写的 linq 运算符(即带有 yield return)已被重写;大概是为了性能,并且可以解释我们的不同结果。

标签: c# performance linq foreach


【解决方案1】:

你忘记在 foreach 之后重置秒表

【讨论】:

  • 即使减去之前的时间,Sum 仍然是 ~15s vs ~3.6s vs .672s
  • @TimS。这就是为什么我认为这是一个调试版本。差异太大,令人难以置信。
【解决方案2】:
  1. 在重新开始之前请致电stopWatch.Reset()。否则,您的性能测试毫无价值 - 您正在总结所有结果。

  2. 您是否在 Visual Studio 之外的 Release 版本上运行测试?

  3. 是的,LINQ 不如 for 循环快。但它更易读,写起来更快。

【讨论】:

  • 代码已修复,在VS外处于发布模式。
【解决方案3】:

您的测试可能存在缺陷,根据MSDN,您需要在每次使用后重置秒表。

话虽如此,LINQ 可能会更慢,但它是否重要是另一回事:

  • 微基准是危险的,因为除非您实际上多次执行该操作,否则性能差异可以忽略不计
  • 一些 LINQ 查询提前退出,因此编写等效的 foreach 并不总是像看起来那么容易。

简而言之,如果您运行探查器并发现真正的瓶颈,请考虑切换(再次运行探查器以确保您已修复它!)不过这确实是逐案处理。

【讨论】:

  • 通过将 Sum 改回 foreach 我确实看到了性能恢复了。得知 Linq 并没有像我想象的那样优化,真是令人沮丧。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
  • 2019-02-09
  • 1970-01-01
  • 2023-03-18
  • 2018-04-17
  • 2012-08-23
相关资源
最近更新 更多