编辑:根据DavidG的建议,我将商品数量提高了100倍。我在发布模式中重新比较并更新了下面的结果。我还更新了代码,以防任何人只是在本地复制和粘贴并运行它。
所以我看到很多关于LINQ与for
表现的帖子 - 其中大部分时间已经有几年了 - 而且我希望看到它对我自己有用。所以我写了一个小应用程序来测试它,结果是...不是我所期望的。 我的问题是:C#6中的更改和优化会使整个性能问题无关紧要吗?
(因为对于很大一部分.NET用户来说,这是一个问题,而不是一个有趣的微优化。有趣的,是的,但不是大多数人需要真正担心的事情。)
我知道从内存占用的角度来看,使用手动循环与LINQ仍然存在很好的问题,但是我的比较应用程序存在严重缺陷,或者看起来不再存在很大的速度差异。也许它在C#的后续版本中被优化了?
我的示例应用程序如下。我承认,这是一种人为的做法 - 这是一个最糟糕的情况,试图找到一个项目,这个项目将在百万项目列表中找到最后一个 - 但我根据这里的其他帖子抓住了它。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace LinqDemo
{
class Program
{
static void Main(string[] args)
{
new Program().Run();
}
public void Run()
{
RunPerformanceComparison();
Console.ReadKey();
}
private void RunPerformanceComparison()
{
Func<string, bool> criteriaFunction = d => d.Equals("YES");
var data = new string[100000000];
for (int i = 0; i < data.Length - 1; i++)
{
data[i] = "NO";
}
data[data.Length - 1] = "YES";
Console.WriteLine("With LINQ");
Console.WriteLine("------------");
DoPerformanceRunLinq(data, criteriaFunction);
Console.WriteLine();
Console.WriteLine("Without LINQ");
Console.WriteLine("------------");
DoPerformanceRunManual(data, criteriaFunction);
}
private void DoPerformanceRunLinq(string[] data, Func<string, bool> criteriaFunction)
{
Stopwatch sw = new Stopwatch();
for (int i = 0; i < 10; i++)
{
sw.Start();
var result = data.Where(criteriaFunction).Select(d => d).ToList();
sw.Stop();
Console.WriteLine($"Iteration {i + 1}\tElapsed: {sw.Elapsed.TotalMilliseconds.ToString("n2")} ms");
sw.Reset();
}
}
private void DoPerformanceRunManual(string[] data, Func<string, bool> criteriaFunction)
{
Stopwatch sw = new Stopwatch();
for (int i = 0; i < 10; i++)
{
sw.Start();
var result = GetItems(data, criteriaFunction);
sw.Stop();
Console.WriteLine($"Iteration {i + 1}\tElapsed: {sw.Elapsed.TotalMilliseconds.ToString("n2")} ms");
sw.Reset();
}
}
private IEnumerable<string> GetItems(string[] data, Func<string, bool> criteriaFunction)
{
var ret = new List<string>();
// Not deferred; runs all at once
for (int i = 0; i < data.Length; i++)
{
if (criteriaFunction(data[i]))
{
ret.Add(data[i]);
}
}
return ret;
}
}
}
这是运行它的输出(我在没有VS的命令行中运行它):
With LINQ ------------ Iteration 1 Elapsed: 602.39 ms Iteration 2 Elapsed: 522.72 ms Iteration 3 Elapsed: 601.15 ms Iteration 4 Elapsed: 518.71 ms Iteration 5 Elapsed: 511.38 ms Iteration 6 Elapsed: 565.92 ms Iteration 7 Elapsed: 506.51 ms Iteration 8 Elapsed: 524.91 ms Iteration 9 Elapsed: 540.85 ms Iteration 10 Elapsed: 502.33 ms Without LINQ ------------ Iteration 1 Elapsed: 496.09 ms Iteration 2 Elapsed: 496.15 ms Iteration 3 Elapsed: 540.53 ms Iteration 4 Elapsed: 549.28 ms Iteration 5 Elapsed: 404.46 ms Iteration 6 Elapsed: 407.23 ms Iteration 7 Elapsed: 461.39 ms Iteration 8 Elapsed: 414.90 ms Iteration 9 Elapsed: 405.67 ms Iteration 10 Elapsed: 437.98 ms
超过100个百万字符串,这在for
方面表现更好,但不是某些人过去声称的大量数据(我听说过10倍的差异。这甚至不是很接近。)。另外,它在内存中有1亿个字符串 - 我认为这里的优化不会通过选择手动循环与LINQ来完成。 :)事实上,我不确定这里的差异是否足以让任何人真正关心,除非你绝对,积极地需要每一个微秒的性能。我基本上称它为洗涤剂。
我在某个地方搞砸了我的应用程序,这只是一个无效的比较,还是内部改变了.NET?
答案 0 :(得分:2)
现在,您正在衡量Where
性能,并在最佳条件下进行测量。它针对一些常见情况进行了优化(具有特殊处理)。您正在将它与数组一起使用,而数组Where
将使用您在“手动”方案中使用的几乎相同的代码来迭代它们。由于只有一个匹配,因此迭代器MoveNext
方法只会被调用一次(好吧,也许两次)。长话短说 - Where
和你的条件中的手动循环具有类似的性能,因为它们运行类似的代码。
如果你想观察(人为地)不良影响而不做太多改变,试试这个:
data.Select(c => c).Where(criteriaFunction).ToList();
现在传递给Where
的不是数组而是“真实”IEnumerable
(由Select
返回),并且数组的特殊处理不适用。我使用此修改运行您的代码,然后Where
执行比手动循环慢4倍。
如果这感觉不公平并且远离实际使用,你可以这样做:
class DataItem {
public string Value { get; set; }
}
// loop version
private IEnumerable<string> GetItems(DataItem[] data, Func<string, bool> criteriaFunction) {
var ret = new List<string>();
// Not deferred; runs all at once
for (int i = 0; i < data.Length; i++) {
if (criteriaFunction(data[i].Value)) {
ret.Add(data[i].Value);
}
}
return ret;
}
// linq version
var result = data.Select(c => c.Value).Where(criteriaFunction).ToList();
这是人们可以真正使用LINQ做的事情,并且它与您的数据的循环版本相差大约4倍。当然LINQ版本可以进行优化,但重点是 - 有些情况下LINQ可能会明显变慢,特别是如果你不小心的话。
有很多这样的例子。考虑这完全无辜的LINQ Count
:
var result = data.Count(c => c.Value == "YES");
与for
类比:
private int ForCount(DataItem[] data) {
int res = 0;
for (int i = 0; i < data.Length; i++) {
if (data[i].Value == "YES") {
res++;
}
}
return res;
}
LINQ慢3倍。等等。