为啥在这个例子中 LINQ 更快
Posted
技术标签:
【中文标题】为啥在这个例子中 LINQ 更快【英文标题】:Why is LINQ faster in this example为什么在这个例子中 LINQ 更快 【发布时间】:2013-06-13 10:20:41 【问题描述】:我写了以下代码来测试使用foreach
与LINQ
的性能:
private class Widget
public string Name get; set;
static void Main(string[] args)
List<Widget> widgets = new List<Widget>();
int found = 0;
for (int i = 0; i <= 500000 - 1; i++)
widgets.Add(new Widget() Name = Guid.NewGuid().ToString() );
DateTime starttime = DateTime.Now;
foreach (Widget w in widgets)
if (w.Name.StartsWith("4"))
found += 1;
Console.WriteLine(found + " - " + DateTime.Now.Subtract(starttime).Milliseconds + " ms");
starttime = DateTime.Now;
found = widgets.Where(a => a.Name.StartsWith("4")).Count();
Console.WriteLine(found + " - " + DateTime.Now.Subtract(starttime).Milliseconds + " ms");
Console.ReadLine();
我得到如下输出:
31160 - 116 毫秒 31160 - 95 毫秒在每次运行中,LINQ 的性能都比 foreach 高 20% 左右。据我了解,LINQ 扩展方法在幕后使用了标准 c#。
那么为什么在这种情况下 LINQ 更快?
编辑:
所以我将代码更改为使用秒表而不是日期时间,但仍然得到相同的结果。如果我先运行 LINQ 查询,那么我的结果显示 LINQ 比 foreach 慢 20%。这必须是某种 JIT 预热问题。我的问题是如何在我的测试用例中补偿 JIT 预热?
【问题讨论】:
您是否尝试过颠倒测试顺序?您可能会看到 JIT 计时。通常最好先运行一次测试以预热系统,然后然后再次运行并计时。另外,使用秒表。见ericlippert.com/tag/benchmarks codereview.stackexchange.com/a/14200 他告诉过你——不计时运行测试,然后使用 Stopwatch 类重新运行测试(更准确)。 别忘了运行Release
模式而不是Debug
模式。它可以产生差异。
@Coltech 要预热代码,只需在开始测试运行之前运行一次,这样任何调用的方法等都将在测试运行开始之前进行 JIT 编译。当我这样做时,我只是在另一个方法中测试了代码,然后在我的计时循环之前调用该方法一次,然后就像在计时循环中一样。
【参考方案1】:
不久前我做了一些分析,比较了以下内容:
LINQ to Objects with/without Regex
带/不带正则表达式的 Lambda 表达式
带有/不带正则表达式的传统迭代
我发现 LINQ、Lambda 和传统迭代几乎总是相同的,但真正的时间差异在于 Regex 表达式。仅添加正则表达式会使评估变慢(慢很多)。 (详情请看:http://www.midniteblog.com/?p=72)
您在上面看到的可能是因为您在同一个代码块中进行了两个测试。尝试评论一个,计时,然后评论另一个。此外,请确保您正在运行发布版本,而不是在调试器中。
【讨论】:
这个问题和正则表达式有什么关系? 这只是分析示例的一部分。随意忽略正则表达式部分。【参考方案2】:这是因为你没有热身。如果你颠倒你的情况,你会得到相反的结果:
31272 - 110ms
31272 - 80 ms
开始添加热身并使用秒表以获得更好的计时。
使用预热运行测试:
//WARM UP:
widgets.Where(a => a.Name.StartsWith("4")).Count();
foreach (Widget w in widgets)
if (w.Name.StartsWith("4"))
found += 1;
//RUN Test
Stopwatch stopwatch1 = new Stopwatch();
stopwatch1.Start();
found = widgets.Where(a => a.Name.StartsWith("4")).Count();
stopwatch1.Stop();
Console.WriteLine(found + " - " + stopwatch1.Elapsed);
found = 0;
Stopwatch stopwatch2 = new Stopwatch();
stopwatch2.Start();
foreach (Widget w in widgets)
if (w.Name.StartsWith("4"))
found += 1;
stopwatch2.Stop();
Console.WriteLine(found + " - " + stopwatch2.Elapsed);
结果:
31039 - 00:00:00.0783508
31039 - 00:00:00.0766299
【讨论】:
您基本上改写了 Jon Skeet 的评论(细节较少,“热身”与 JIT)。您应该添加示例和更多解释... 谢谢。我将使用秒表而不是日期时间。你能定义一下“热身”是什么意思吗? 您两次都在测试相同的方法,但您仍在使用 DateTime 来获取计时... 使用此代码并用秒表替换 datetime 产生了预期的结果。谢谢! 将日期时间更改为秒表实现以上是关于为啥在这个例子中 LINQ 更快的主要内容,如果未能解决你的问题,请参考以下文章
基本反应问题。为啥在 React 文档的这个例子中需要 useEffect ?
为啥 Map 在这个例子中有一个 return 语句? [复制]