为什么LINQ .Where(predicate).First()比.First(predicate)快?


73

我正在做一些性能测试,发现LINQ表达式像

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

比慢

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

这似乎与直觉相反。我以为第一个表达式会更快,因为一旦满足谓词,它就可以停止对列表进行迭代,而我以为该.Where()表达式可以在调用.First()结果子集之前对整个列表进行迭代。即使后者短路,也不应比直接使用First更快。

下面是两个非常简单的单元测试,它们说明了这一点。当在.NET和Silverlight 4上使用TestWhereAndFirst进行优化编译时,其速度大约比TestFirstOnly快30%。我试图使谓词返回更多结果,但是性能差异是相同的。

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

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);
}

5
但是,您最初的想法是错误的:LINQ确实进行了延迟计算,因此当First()调用LINQ时 ,它将仅查询(Where(...)一个)匹配项(的返回值),而从不要求另一个匹配项。因此,将检查与调用时完全相同数量的元素First(...)(即直接使用谓词)。
乔恩

1
我得到的结果相同,分别.Where().First()为.021秒和.First().037秒。这是一个简单的ints列表。
Ry-


根据我的测试,它还取决于您要寻找的元素。在应用Where和first谓词时,请尝试使用特定的i值。我尝试使用值1和更高的4999。我发现结果有所不同。似乎“第一”遍历每个项目并匹配垂直谓词,直到匹配为止。
dotnetstep 2011年

5
@minitech您没有打电话Reset()给秒表;您的测试实际上表明First()速度显着提高。
杰伊(Jay)

Answers:


50

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

正如乔恩(Jon)所述,Linq使用惰性评估,因此两种方法的性能应该(并且应该)大致相似。

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


11
但是,如果我是框架开发人员,并且只是在内部将First(fn)实现为return Where(fn).First(),它将与First的当前实现完全一样,只是速度更快!微软方面似乎受到了不良监督。
dazza

1
相当。我想没人会想到。
arx

5
还要将.Count(fn)与.Where(fn).Count()比较。后者由于使用了专门的迭代器而不是.Count(fn)使用的foreach,因此速度更快。使用.First(fn)和.Count(fn)之类的便捷方法可以使代码更简洁,因此似乎可以,但是.Where(fn).Method()明显更快。rr!
dazza 2011年

5
看看dotnetfiddle.net/k11nX6,答案会是错误的,您会感到惊讶。
Akash Kava 2015年

7
立即尝试!!!dotnetfiddle.net/OrUUSG,您的答案是错误的!尝试任何组合并证明您的答案正确。真正的答案是可枚举将Enumerable缓存在何处,这比旧的List / Array迭代器有优势,但与快速无关。
Akash Kava
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.