【问题标题】:Why can LINQ operations be faster than a normal loop?为什么 LINQ 操作可以比普通循环更快?
【发布时间】:2010-09-21 00:38:15
【问题描述】:

今天在一次编程讨论中,我和一个朋友有点困惑。例如,我们创建了一个虚构的问题,即有一个包含 n 个随机整数(通常为 1.000.000)的List<int>,并希望创建一个函数来返回所有整数的集合,其中包含多个整数。很简单的东西。我们创建了一个 LINQ 语句来解决这个问题,以及一个基于普通插入排序的算法。

现在,当我们测试代码运行的速度时(使用System.Diagnostics.StopWatch),结果令人困惑。 LINQ 代码不仅性能优于简单排序,而且运行速度也比 单个 foreach/for 更快,在旁道上,我认为编译器应该一起发现并删除)。

如果我们在程序的同一执行过程中生成一个新的随机数List<int> 并再次运行 LINQ 代码,则性能将提高几个数量级(通常是千倍)。空循环的性能当然是一样的。

那么,这里发生了什么? LINQ 是否使用并行性来胜过正常循环?这些结果怎么可能? LINQ 使用以 n*log(n) 运行的快速排序,根据定义,它已经比 n 慢。

第二次运行的性能飞跃发生了什么?

我们对这些结果既困惑又好奇,并希望社区提供一些澄清的见解,以满足我们自己的好奇心。

【问题讨论】:

  • 请贴一些代码。
  • 也许你可以分享你的测试?
  • 发布您的代码。你可能没有考虑deferred execution
  • 我非常有兴趣查看该代码并亲自使用它......但你说它甚至优于空循环这一事实强烈表明你没有在正确的地方停止观看代码,根据 Matthew Flaschen 的评论。
  • “Linq 使用以 nlog(n) 运行的快速排序,根据定义,它已经比 n 慢。”不对。 nlog(n)渐近地比 n 慢,但是常数因子可能会导致在实践中,n*log(n) 算法比线性算法更快某些数据集。

标签: c# linq algorithm performance sorting


【解决方案1】:

毫无疑问,您实际上并没有执行查询,您只是定义了它。 LINQ 构造一个表达式树,在您执行需要迭代枚举的操作之前,该树实际上不会被计算。尝试向 LINQ 查询添加 ToList()Count() 操作以强制评估查询。

根据您的评论,我希望这与您所做的类似。注意:我没有花时间弄清楚查询是否尽可能高效;我只想一些查询来说明代码的结构。

var dataset = ...

var watch = Stopwatch.StartNew();

var query = dataset.Where( d => dataset.Count( i => i == d ) > 1 );

watch.Stop();  // timer stops here

foreach (var item in query) // query is actually evaluated here
{
   ... print out the item...
}

【讨论】:

  • 否定:我们打印了数据集以验证它确实在做它正在做的事情。
  • @Pedery -- 但是你是从定时代码部分还是在它之外打印出来的。迭代它以打印它会导致它被评估,但如果你没有为打印它的循环计时,那么评估将在计时循环之外完成。
  • 我们将 linq 的结果分配给一个 var,然后将其打印在定时区域之外。
  • 是的,我看到了你的编辑。关于性能和计时器,您的示例类似于我们的代码。我认为这可以解释问题。
  • 请参阅有关“延迟执行”和 Linq 的大量主题。这些在 IEnumerable (DataSet.Where, et. Al) 和 IQueryable (对于实体和 Linq-to-Sql) 中都公开。优点是构建一个大型 Linq 查询,而无需触及您正在查询的对象,直到绝对需要检索(代码的 foreach var item 部分)。此时,一个枚举器建立在查询的结果集合之上。
【解决方案2】:

我建议仅当您的算法不够完美(或者您的代码有问题)时,LINQ 才比“正常循环”更快。因此,如果您不编写高效的排序算法等,LINQ 在排序方面会比您更快。

LINQ 通常与正常循环的速度“一样快”或“足够接近”,并且可以更快(更简单)编码/调试/读取。这是它的好处 - 而不是执行速度。

如果它的执行速度比空循环快,则说明您做错了。很可能,正如 cmets 中所建议的那样,您没有考虑延迟执行并且 LINQ 语句实际上并未执行。

【讨论】:

  • 正如我在上面的评论中所说,我们打印了它正在生成的数据集,一切似乎都井井有条。
  • 您必须在秒表停止之前打印它。但是,打印速度太慢 - 最好将其转储到列表中。
  • 呵呵,将我写的任何东西与 BCL 中的任何东西进行比较,是的,我所有的代码都不够完美。 :O
【解决方案3】:

如果您没有启用“优化代码”进行编译,您可能会看到这种行为。 (它肯定会解释为什么没有删除空循环。)

然而,LINQ 的底层代码是已编译代码的一部分,肯定已经过优化(通过 JIT、NGen 或类似代码)。

【讨论】:

  • +1 好点!我忘记检查计算机的 VS 默认设置关于代码优化。
猜你喜欢
  • 1970-01-01
  • 2016-05-07
  • 2012-07-18
  • 2018-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-20
相关资源
最近更新 更多