为啥在这个例子中 LINQ 更快

Posted

技术标签:

【中文标题】为啥在这个例子中 LINQ 更快【英文标题】:Why is LINQ faster in this example为什么在这个例子中 LINQ 更快 【发布时间】:2013-06-13 10:20:41 【问题描述】:

我写了以下代码来测试使用foreachLINQ 的性能:

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 语句? [复制]

为啥在这个例子中 PyCUDA 比 C CUDA 快

为啥 strpos 有用/使用,它在这个例子中有啥好处? [关闭]

为啥 datetime.strptime 在这个简单的例子中不起作用?