【问题标题】:Why is LINQ .Where(predicate).First() faster than .First(predicate)?为什么 LINQ .Where(predicate).First() 比 .First(predicate) 快?
【发布时间】:2012-01-29 15:18:03
【问题描述】:

我正在做一些性能测试,并注意到像 LINQ 表达式

result = list.First(f => f.Id == i).Property

result = list.Where(f => f.Id == i).First().Property

这似乎违反直觉。我原以为第一个表达式会更快,因为它可以在满足谓词后立即停止遍历列表,而我原以为 .Where() 表达式可能会在调用 .First() 之前遍历整个列表结果子集。即使后者确实短路,它也不应该比直接使用 First 更快,但确实如此。

下面是两个非常简单的单元测试来说明这一点。在 TestWhereAndFirst 上进行优化编译时,在 .Net 和 Silverlight 4 上比 TestFirstOnly 快大约 30%。我尝试让谓词返回更多结果,但性能差异是相同的。

谁能解释为什么.First(fn).Where(fn).First() 慢?与.Where(fn).Count() 相比,我看到.Count(fn) 的类似反直觉结果。

private const int Range = 50000;

private class Simple
{
   public int Id { get; set; }
   public int Value { get; set; }
}

[TestMethod()]
public void TestFirstOnly()
{
   List<Simple> list = new List<Simple>(Range);
   for (int i = Range - 1; i >= 0; --i)
   {
      list.Add(new Simple { Id = i, Value = 10 });
   }

   int result = 0;
   for (int i = 0; i < Range; ++i)
   {
      result += list.First(f => f.Id == i).Value;
   }

   Assert.IsTrue(result > 0);
}

[TestMethod()]
public void TestWhereAndFirst()
{
   List<Simple> list = new List<Simple>(Range);
   for (int i = Range - 1; i >= 0; --i)
   {
      list.Add(new Simple { Id = i, Value = 10 });
   }

   int result = 0;
   for (int i = 0; i < Range; ++i)
   {
      result += list.Where(f => f.Id == i).First().Value;
   }

   Assert.IsTrue(result > 0);
}

【问题讨论】:

  • 您最初的想法是错误的:LINQ 执行惰性计算,因此当调用 First() 时,它只会查询(返回值)Where(...) 的一个匹配项,而不会再要求另一个匹配项。因此,将检查与调用 First(...) 时完全相同数量的元素(即直接使用谓词)。
  • 我得到相同的结果,.Where().First() 是 0.021 秒,.First() 是 0.037 秒。这是一个简单的ints 列表。
  • 根据我的测试,它还取决于您要查找的元素。只需在应用 Where 和第一个谓词时尝试使用特定的 i 值。我尝试使用值 1 和更高版本的 4999。我看到了结果的差异。似乎 First 循环遍历每个项目并匹配特定谓词,直到匹配为止。
  • @minitech 你没有在秒表上打电话给Reset();您的测试实际上表明 First() 明显更快。

标签: c# .net performance linq


【解决方案1】:

我得到了相同的结果:where+first 比 first 快。

正如 Jon 所说,Linq 使用惰性求值,因此这两种方法的性能应该(并且现在)大致相似。

在 Reflector 中,First 使用一个简单的 foreach 循环来迭代集合,但 Where 有多种迭代器专门用于不同的集合类型(数组、列表等)。大概这就是 Where 小优势的原因。

【讨论】:

  • 但是如果我是一个框架开发人员并且只是在内部实现了 First(fn) 作为 return Where(fn).First() 它将完全像当前的 First 实现一样工作,除了更快!似乎是微软的一个糟糕的疏忽。
  • 相当。我想没有人想过。
  • 还将 .Count(fn) 与 .Where(fn).Count() 进行比较。后者由于使用专门的迭代器而不是 .Count(fn) 使用的 foreach 而更快 使用 .First(fn) 和 .Count(fn) 之类的便捷方法会导致代码更简洁,因此看起来是正确的做,但 .Where(fn).Method() 明显更快。呸!
  • 看看dotnetfiddle.net/k11nX6,你会惊讶于答案是错误的。
  • 现在试试这个!!! dotnetfiddle.net/OrUUSG,你的回答错了!!!尝试任何组合并证明你的答案是正确的。真正的答案是可枚举缓存 Enumerable 比旧的 List/Array 迭代器有优势,但这与快速无关。
猜你喜欢
  • 2020-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-13
相关资源
最近更新 更多