【发布时间】: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