我只想知道“FindAll”是否会比“Where”extentionMethod更快以及为什么?
示例:
myList.FindAll(item=> item.category == 5);
或
myList.Where(item=> item.category == 5);
哪个更好?
答案 0 :(得分:50)
好吧,FindAll
将匹配的元素复制到一个新列表,而Where
只返回一个延迟评估的序列 - 不需要复制。
我希望Where
比FindAll
略快,即使对结果序列进行了全面评估 - 当然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()等的效果。我不确定,但我猜你在延迟执行的所有函数中都有很大的优势