【问题标题】: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
  • 他告诉过你——不计时运行测试,然后使用 Stopwatch 类重新运行测试(更准确)。
  • 别忘了运行Release 模式而不是Debug 模式。它可以产生差异。
  • @Coltech 要预热代码,只需在开始测试运行之前运行一次,这样任何调用的方法等都将在测试运行开始之前进行 JIT 编译。当我这样做时,我只是在另一个方法中测试了代码,然后在我的计时循环之前调用该方法一次,然后就像在计时循环中一样。

标签: c# performance linq foreach


【解决方案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 产生了预期的结果。谢谢!
  • 将日期时间更改为秒表实现
猜你喜欢
  • 2021-05-31
  • 2016-11-24
  • 1970-01-01
  • 2014-11-23
  • 1970-01-01
  • 1970-01-01
  • 2015-04-16
  • 1970-01-01
  • 2014-07-07
相关资源
最近更新 更多