FindAll vs Where扩展方法

时间:2009-10-07 13:39:53

标签: c# lambda extension-methods

我只想知道“FindAll”是否会比“Where”extentionMethod更快以及为什么?

示例:

myList.FindAll(item=> item.category == 5);

myList.Where(item=> item.category == 5);

哪个更好?

5 个答案:

答案 0 :(得分:50)

好吧,FindAll将匹配的元素复制到一个新列表,而Where只返回一个延迟评估的序列 - 不需要复制。

我希望WhereFindAll略快,即使对结果序列进行了全面评估 - 当然Where的懒惰评估策略意味着如果你只是看看(比方说)第一场比赛,它不需要检查列表的其余部分。 (正如Matthew所指出的那样,维护Where的状态机是有效的。但是,这只会产生固定的内存成本 - 而构建新的列表可能需要多个数组分配等。)

基本上,FindAll(predicate)更接近Where(predicate).ToList()而非Where(predicate)

为了对马修的答案做出更多反应,我认为他的测试不够彻底。他的谓词碰巧选择一半项目。这是一个简短但完整的程序,它测试相同的列表,但有三个不同的谓词 - 一个选择没有项目,一个选择所有项目,一个选择其中一半。在每种情况下,我都会进行五十次测试以获得更长的时间。

我正在使用Count()来确保完全评估Where结果。结果显示,收集约一半的结果,两个是颈部和颈部。收集没有结果,FindAll获胜。收集所有结果,Where获胜。我发现这很有趣:随着越来越多的匹配被发现,所有解决方案变得越来越慢:FindAll有更多的复制要做,而Where必须返回匹配的值,而不是仅仅在{{ 1}}实施。但是,MoveNext()的速度比FindAll慢得多,因此失去了早期的领先优势。非常有趣。

结果:

Where

(使用/ o + / debug-编译并从命令行运行,.NET 3.5。)

代码:

FindAll: All: 11994
Where: All: 8176
FindAll: Half: 6887
Where: Half: 6844
FindAll: None: 3253
Where: None: 4891

答案 1 :(得分:14)

我们测试而不是猜测?很遗憾看到错误的答案出来了。

var ints = Enumerable.Range(0, 10000000).ToList();
var sw1 = Stopwatch.StartNew();
var findall = ints.FindAll(i => i % 2 == 0);
sw1.Stop();

var sw2 = Stopwatch.StartNew();
var where = ints.Where(i => i % 2 == 0).ToList();
sw2.Stop();

Console.WriteLine("sw1: {0}", sw1.ElapsedTicks);
Console.WriteLine("sw2: {0}", sw2.ElapsedTicks);
/*
Debug
sw1: 1149856
sw2: 1652284

Release
sw1: 532194
sw2: 1016524
*/

修改

即使我从

转动上述代码
var findall = ints.FindAll(i => i % 2 == 0);
...
var where = ints.Where(i => i % 2 == 0).ToList();

...到......

var findall = ints.FindAll(i => i % 2 == 0).Count;
...
var where = ints.Where(i => i % 2 == 0).Count();

我得到了这些结果

/*
Debug
sw1: 1250409
sw2: 1267016

Release
sw1: 539536
sw2: 600361
*/

编辑2.0 ...

如果你想要一个当前列表子集的列表,那么FindAll()的最快方法。这样做的原因是简单的。 FindAll实例方法使用当前List上的索引器而不是枚举器状态机。 Where()扩展方法是对使用枚举器的其他类的外部调用。如果从列表中的每个节点步进到下一个节点,则必须调用封面下的MoveNext()方法。从上面的示例中可以看出,使用索引条目创建新列表(指向原始项目,因此内存膨胀最小)甚至只是获取已过滤项目的计数甚至更快。

现在,如果您要从Enumerator中提前中止,Where()方法可能会更快。当然,如果将早期中止逻辑移动到FindAll()方法的谓词,您将再次使用索引器而不是枚举器。

现在还有其他原因要使用Where()语句(例如其他linq方法,foreach块等等),但问题是FindAll()比Where()更快。除非你不执行Where(),否则答案似乎是肯定的。 (比较苹果和苹果)

我不是说不要使用LINQ或.Where()方法。它们使代码更易于阅读。问题是关于性能而不是关于阅读和理解代码的容易程度。快速执行此工作的最快方法是使用for block步进每个索引并根据需要执行任何逻辑(甚至是早期退出)。 LINQ之所以如此伟大,是因为复杂的表达式树和你可以得到的转换。但是使用.Where()方法中的迭代器必须经过大量代码才能找到内存状态机的方法,这只是将下一个索引从List中取出。还应该注意,这个.FindAll()方法仅对实现它的对象有用(例如Array和List。)

更多......

for (int x = 0; x < 20; x++)
{
    var ints = Enumerable.Range(0, 10000000).ToList();
    var sw1 = Stopwatch.StartNew();
    var findall = ints.FindAll(i => i % 2 == 0).Count;
    sw1.Stop();

    var sw2 = Stopwatch.StartNew();
    var where = ints.AsEnumerable().Where(i => i % 2 == 0).Count();
    sw2.Stop();

    var sw4 = Stopwatch.StartNew();
    var cntForeach = 0;
    foreach (var item in ints)
        if (item % 2 == 0)
            cntForeach++; 
    sw4.Stop();

    Console.WriteLine("sw1: {0}", sw1.ElapsedTicks);
    Console.WriteLine("sw2: {0}", sw2.ElapsedTicks);
    Console.WriteLine("sw4: {0}", sw4.ElapsedTicks);
}


/* averaged results
sw1 575446.8
sw2 605954.05
sw3 394506.4
/*

答案 2 :(得分:2)

嗯,至少你可以尝试测量它。

使用迭代器bloc(Where关键字)实现静态yield方法,这基本上意味着将延迟执行。如果你只比较对这两种方法的调用,第一种方法会慢一些,因为它会立即暗示整个集合将被迭代。

但如果你包括你得到的结果的完整迭代,事情可能会有所不同。由于它所暗示的生成的状态机机制,我很确定yield解决方案较慢。 (见@Matthew anwser)

答案 3 :(得分:1)

我可以提供一些线索,但不确定哪一个更快。 FindAll()立即执行。 Where()被执行defferred。

答案 4 :(得分:0)

延迟执行的优势在哪里。如果您具有以下功能,请查看区别

BigSequence.FindAll( x =>  DoIt(x) ).First();
BigSequence.Where( x => DoIt(x) ).First();

FindAll已经涵盖了完整的序列,而大多数序列中的位置只要找到一个元素就会停止枚举。

相同的效果将是使用Any(),Take(),Skip()等的效果。我不确定,但我猜你在延迟执行的所有函数中都有很大的优势