C# FindAll VS Where 速度

Posted

技术标签:

【中文标题】C# FindAll VS Where 速度【英文标题】:C# FindAll VS Where Speed 【发布时间】:2011-01-16 15:25:30 【问题描述】:

任何人都知道 Where 和 FindAll on List 之间的速度差异。我知道 Where 是 IEnumerable 的一部分,而 FindAll 是 List 的一部分,我只是好奇什么更快。

【问题讨论】:

FindAll vs Where extension-method的可能重复 【参考方案1】:

List 类的 FindAll 方法实际上构造了一个新的列表对象,并将结果添加到其中。 IEnumerable 的 Where 扩展方法将简单地遍历现有列表并生成匹配结果的枚举,而无需创建或添加任何内容(除了枚举器本身)。

给定一个小集合,两者的表现可能相当。但是,给定一个更大的集合,Where 应该优于 FindAll,因为为包含结果而创建的新 List 必须动态增长以包含其他结果。随着匹配结果数量的增加,FindAll 的内存使用量也将开始呈指数增长,而 Where 应该具有恒定的最小内存使用量(本身......不包括你对结果所做的任何事情。)

【讨论】:

例外情况是您确实希望之后有一个列表(也许您需要调用Count 或更改成员,或多次迭代它)。 Where() 击败 FindAll()FindAll() 击败 Where().ToList() @JonHanna:起初我以为我同意了,但实际上我并不确定。您是否有任何引用表明 .ToList() 比 .FindAll() 慢?在查询上调用 .ToList() 将可枚举的迭代,因此应该保持其内存效率。不仅如此, where 迭代器的某些内部实现甚至可以预先创建一个大小(内存分配)完全正确的列表,在这种情况下优于 FindAll。我并没有特别反对,但是如果有一个可靠的参考来阐明 FindAlls 的好处,那就太好了。 这个答案大错特错。看看@Wiory,他不屑于实际测量。 @Carlo:对不起,但实际上是你错了。您对 Wiory 的回答的评论似乎没有注意到他确实通过“Check()”方法枚举了每种方法......包括 where->ienum 方法。 Wiory 的结果验证了我的回答……FindAll 比使用 Where 慢。此外,针对不同类型的底层集合的 Where 的各种实现通常针对集合的特定机制进行了优化,从而提供了进一步的性能提升(即,它并非都是纯粹的通用“where”行为......它可能非常有效! )【参考方案2】:

FindAll 显然比 Where 慢,因为它需要创建一个新列表。

无论如何,我认为您真的应该考虑 Jon Hanna 的评论 - 您可能需要对结果进行一些操作,并且 list 在许多情况下会比 IEnumerable 更有用。

我写了一个小测试,只是将它粘贴到控制台应用程序项目中。它测量时间/滴答声:函数执行、对结果集合的操作(以获得“真实”使用的性能,并确保编译器不会优化未使用的数据等。 - 我是 C# 新手,不会知道它是如何工作的,抱歉)。

注意:除 WhereIENumerable() 之外的每个测量函数都会创建新的元素列表。我可能做错了什么,但显然迭代 IEnumerable 比迭代列表花费更多的时间。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Tests


    public class Dummy
    
        public int Val;
        public Dummy(int val)
        
            Val = val;
        
    
    public class WhereOrFindAll
    
        const int ElCount = 20000000;
        const int FilterVal =1000;
        const int MaxVal = 2000;
        const bool CheckSum = true; // Checks sum of elements in list of resutls
        static List<Dummy> list = new List<Dummy>();
        public delegate void FuncToTest();

        public static long TestTicks(FuncToTest function, string msg)
        
            Stopwatch watch = new Stopwatch();
            watch.Start();
            function();
            watch.Stop();
            Console.Write("\r\n"+msg + "\t ticks: " + (watch.ElapsedTicks));
            return watch.ElapsedTicks;
        
        static void Check(List<Dummy> list)
        
            if (!CheckSum) return;
            Stopwatch watch = new Stopwatch();
            watch.Start();

            long res=0;
            int count = list.Count;
            for (int i = 0; i < count; i++)     res += list[i].Val;
            for (int i = 0; i < count; i++)     res -= (long)(list[i].Val * 0.3);

            watch.Stop();
            Console.Write("\r\n\nCheck sum: " + res.ToString() + "\t iteration ticks: " + watch.ElapsedTicks);
        
        static void Check(IEnumerable<Dummy> ieNumerable)
        
            if (!CheckSum) return;
            Stopwatch watch = new Stopwatch();
            watch.Start();

            IEnumerator<Dummy> ieNumerator = ieNumerable.GetEnumerator();
            long res = 0;
            while (ieNumerator.MoveNext())  res += ieNumerator.Current.Val;
            ieNumerator=ieNumerable.GetEnumerator();
            while (ieNumerator.MoveNext())  res -= (long)(ieNumerator.Current.Val * 0.3);

            watch.Stop();
            Console.Write("\r\n\nCheck sum: " + res.ToString() + "\t iteration ticks :" + watch.ElapsedTicks);
        
        static void Generate()
        
            if (list.Count > 0)
                return;
            var rand = new Random();
            for (int i = 0; i < ElCount; i++)
                list.Add(new Dummy(rand.Next(MaxVal)));

        
        static void For()
        
            List<Dummy> resList = new List<Dummy>();
            int count = list.Count;
            for (int i = 0; i < count; i++)
            
                if (list[i].Val < FilterVal)
                    resList.Add(list[i]);
                  
            Check(resList);
        
        static void Foreach()
        
            List<Dummy> resList = new List<Dummy>();
            int count = list.Count;
            foreach (Dummy dummy in list)
            
                if (dummy.Val < FilterVal)
                    resList.Add(dummy);
            
            Check(resList);
        
        static void WhereToList()
        
            List<Dummy> resList = list.Where(x => x.Val < FilterVal).ToList<Dummy>();
            Check(resList);
        
        static void WhereIEnumerable()
        
            Stopwatch watch = new Stopwatch();
            IEnumerable<Dummy> iEnumerable = list.Where(x => x.Val < FilterVal);
            Check(iEnumerable);
        
        static void FindAll()
        
            List<Dummy> resList = list.FindAll(x => x.Val < FilterVal);
            Check(resList);
        
        public static void Run()
        
            Generate();
            long[] ticks =  0, 0, 0, 0, 0 ;
            for (int i = 0; i < 10; i++)
            
                ticks[0] += TestTicks(For, "For \t\t");
                ticks[1] += TestTicks(Foreach, "Foreach \t");
                ticks[2] += TestTicks(WhereToList, "Where to list \t");
                ticks[3] += TestTicks(WhereIEnumerable, "Where Ienum \t");
                ticks[4] += TestTicks(FindAll, "FindAll \t");
                Console.Write("\r\n---------------");
            
            for (int i = 0; i < 5; i++)
                Console.Write("\r\n"+ticks[i].ToString());
        
    
    

    class Program
    
        static void Main(string[] args)
        
            WhereOrFindAll.Run();
            Console.Read();
        
    

Results(ticks) - 启用 CheckSum(对结果进行一些操作),模式:不调试释放(CTRL+F5):

 - 16,222,276 (for ->list)
 - 17,151,121 (foreach -> list)
 -  4,741,494 (where ->list)
 - 27,122,285 (where ->ienum)
 - 18,821,571 (findall ->list)

CheckSum 已禁用(根本不使用返回的列表):

 - 10,885,004 (for ->list)
 - 11,221,888 (foreach ->list)
 - 18,688,433 (where ->list)
 -      1,075 (where ->ienum)
 - 13,720,243 (findall ->list)

您的结果可能会略有不同,要获得真正的结果,您需要更多的迭代。

【讨论】:

你的测试很好。它们表明 LINQ 机制比直接对列表进行操作要慢。并不意外。您的“1075(where ->ienum)”是错误的,因为使用 where 而不遍历结果元素将永远不会真正执行 where! 对不起 Carlo,但他甚至在 where->ienum 实现中也调用了他的“Check()”方法。 Check() 迭代所有的集合,所以他的结果是完全有效的。结果,这也使我的答案正确……您称之为“大错特错”的答案。【参考方案3】:

更新(来自评论):查看该代码我同意,.Where 应该有,在最坏的情况下,相同的性能,但几乎总是更好。

原始答案:.FindAll() 应该更快,它利用已经知道 List 的大小并使用简单的 for 循环遍历内部数组的优势。 .Where() 必须启动一个枚举器(在这种情况下是一个名为 WhereIterator 的密封框架类)并以不太具体的方式完成相同的工作。

但请记住,.Where() 是可枚举的,而不是主动在内存中创建 List 并填充它。它更像是一个流,因此非常大的东西的内存使用可能会有很大的不同。此外,您可以在 4.0 中使用 .Where() 方法更快地以并行方式开始使用结果。

【讨论】:

实际使用的是 WhereEnumerableIterator,而不是 WhereIterator,除非您在 where 子句中涉及索引。 WhereEnumerableIterator 比 WhereIterator 高效得多。在 List 的情况下,它会产生额外的方法调用成本(应该在发布代码中内联),但不需要在处理过程中动态调整内部列表的大小。除最小列表外,Where 的效率应该优于 FindAll(任何大于 4 个结果的结果都将导致一次或多次调整大小。) 在调用 Where on an Array 或 List 的情况下,还有两个额外的内部迭代器类 WhereArrayIterator 和 WhereListIterator,它们针对这两种情况进行了优化。一般来说,调用 Where 应该比调用 FindAll 更高效。 @jrista - 我完全错过了 .Where() 重载返回的案例堆栈,谢谢!浏览该代码我同意, .Where 应该有,在最坏的情况下,相同的性能,但几乎总是更好。此外,如果不是人们花额外的时间来教育他人,那么 SO 将毫无用处,例如你和这些 cmets,+1 教我一些东西。 很高兴能为您服务。 :)【参考方案4】:

WhereFindAll 快得多。不管列表有多大,Where 花费的时间完全相同。

当然Where 只是创建一个查询。它实际上并没有做任何事情,不像FindAll 会创建一个列表。

【讨论】:

这在技术上可能是正确的,但我认为很明显 OP 是在实际枚举结果的上下文中询问性能,而不是裸方法调用本身。【参考方案5】:

jrista 的回答很有道理。但是,新列表添加了相同的对象,因此只是参考现有对象增长,这不应该那么慢。 只要 3.5/Linq 扩展是可能的,Where 还是会更好。 FindAll 在受限于 2.0 时更有意义

【讨论】:

以上是关于C# FindAll VS Where 速度的主要内容,如果未能解决你的问题,请参考以下文章

GRAILS:findALL() vs FindBy---(params.id)

Sequelize findAll() with where-parameter 返回 null 而 findByPk() 返回正确的数据

IN vs = in Where 子句

C# Datagrid 的 WHERE 子句的文本框

JpaRepository vs CRUDRepository findAll

欧姆龙FINS TCP协议,C#通信设计实例