Linq的Count()比List.Count或Array.Length慢还是快?

61

LINQ的Count()方法和 List<>.Count 或者 Array.Length哪个更快或更慢?


11
了解最简单的方法是尝试它。 在StopWatch的适当方法中包裹两者,并重复执行几百万次,你就会知道。 - Eric Lippert
2
值得注意的是,除非我们谈论的是一些非常大的集合,否则速度上不会有明显的差异。只需使用更易于阅读/维护的那个即可。 - Hardwareguy
6个回答

73

一般来说比较慢。LINQ的Count方法通常是一个O(N)的操作,然而List.CountArray.Length都保证是O(1)的。

但在某些情况下,LINQ会特殊处理IEnumerable<T>参数,将其转换为某些接口类型,例如IList<T>ICollection<T>。 然后它将使用该Count方法执行实际的Count()操作。 因此,它将返回O(1)。 但您仍需要支付进行转换和接口调用的微小开销。


我不确定,但我认为如果在IQueryable上运行List.Count(),它将执行select count(*) sql命令。但是如果运行List.Count,它将枚举所有项目,然后返回计数。如果是后者,大多数情况下List.Count()会更快。 - Jose
@Jared,Marc的回答更准确,检查仅针对ICollection<T>,数组,HashSet,Dictionary,List,LinkedList和Queue实现了ICollection<T>。旧的System.Collection类没有实现IEnumerable<T>,因此它们无法与LINQ一起使用。 - Sam Saffron
@sambo99,是的,我应该添加通用规范(懒得想如何影响我的答案)。很快就会加上。 - JaredPar
你可以在这里查看 Enumerable.Count() 算法:https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,41ef9e39e54d0d0b - Creak

28

Enumerable.Count() 方法检查 ICollection<T>,使用 .Count - 所以对于数组和列表而言,并没有太多的效率损失(只是额外增加了一层间接性)。


实际上,使用数组可以获得2个间接层,详见我的回答 :p - Sam Saffron

25

Marc的答案是正确的,但魔鬼在于细节。

在我的机器上:

  • 对于数组,.Length比.Count()快约100倍
  • 对于列表,.Count比.Count()快约10倍 - 注意:我希望所有实现了 IList<T> 接口的集合都有类似的性能表现。

数组一开始较慢,因为.Length只涉及单个操作,而数组上的.Count涉及一层间接操作。因此,在我的机器上,数组上的.Count开始时会慢10倍,这可能是接口实现显式的原因之一。想象一下,如果您有一个具有两个公共属性.Count和.Length的对象。两者都做同样的事情,但.Count要慢10倍。

当然,所有这些都不会产生太大的影响,因为您必须每秒计数数百万次的数组和列表才能感受到性能损失。

代码:

    static void TimeAction(string description, int times, Action func) {
        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < times; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
    } 

    static void Main(string[] args) {
        var array = Enumerable.Range(0, 10000000).ToArray();
        var list = Enumerable.Range(0, 10000000).ToArray().ToList();

        // jit
        TimeAction("Ignore and jit", 1 ,() =>
        {
            var junk = array.Length;
            var junk2 = list.Count;
            array.Count();
            list.Count();
        });


        TimeAction("Array Length", 1000000, () => {
            var tmp1 = array.Length;
        });

        TimeAction("Array Count()", 1000000, () =>
        {
            var tmp2 = array.Count();
        });

        TimeAction("Array Length through cast", 1000000, () =>
        {
            var tmp3 = (array as ICollection<int>).Count;
        });


        TimeAction("List Count", 1000000, () =>
        {
            var tmp1 = list.Count;
        });

        TimeAction("List Count()", 1000000, () =>
        {
            var tmp2 = list.Count();
        });

        Console.ReadKey();
    }

结果:

数组长度 时间消耗 3 毫秒
数组 Count() 时间消耗 264 毫秒
通过转换获得数组长度 时间消耗 16 毫秒
列表 Count 时间消耗 3 毫秒
列表 Count() 时间消耗 18 毫秒

1
幸运的是,collection.Count/Lengthcollection.Count()更易读。很少有情况下漂亮的代码会更高效 :P - nawfal
1
只是提供信息,我看到(array as ICollection<int>).Count;(array as ICollection).Count;之间有一些细微的差别(前者更好)。 - jmoreno

4

我认为这取决于列表的类型。如果它是一个IQueryable,是数据库中的表格,那么Count()会快得多,因为它不需要加载所有对象。但如果列表在内存中,我猜想Count属性应该会更快,如果不是同样的话。


3

我认为,如果你在 ICollection 或 IList(如 ArrayList 或 List)上调用 Linq.Count(),它将只返回 Count 属性的值。因此,在普通集合上的性能将大致相同。


ArrayList不是IEnumerable<T>,因此您无法在其上执行LINQ扩展方法。这个检查只针对ICollection<T>。 - Sam Saffron

1

一些额外的信息 - LINQ Count - 使用它和不使用它之间的差异可能是巨大的 - 而且这并不一定是针对“大”集合。我有一个由linq to objects形成的集合,其中约有6500个项目(很大..但绝不是巨大的)。在我的情况下,Count()需要几秒钟的时间。将其转换为列表(或数组,无论哪种)后,计数就几乎立即完成了。在内部循环中拥有此计数意味着影响可能是巨大的。Count枚举所有内容。数组和列表都“自我感知”其长度,无需枚举它们。任何引用此count()的调试语句(例如log4net)也会使一切变慢得多。如果您经常需要引用此内容,请自己做个好人,保存计数大小,并仅在LINQ集合上调用一次,除非您将其转换为列表,然后可以引用而不会影响性能。

这是我上面谈到的快速测试。请注意,每次调用Count()时,我们的集合大小都会发生变化..因此会发生评估,这比预期的“计数”操作更多。只是要注意一下 :)

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

    namespace LinqTest
    {
        class TestClass
        {
            public TestClass()
            {
                CreateDate = DateTime.Now;
            }
            public DateTime CreateDate;
        }

        class Program
        {

            static void Main(string[] args)
            {
                //填充测试类
                List list = new List(1000);
                for (int i=0; i<1000; i++)
                {
                    System.Threading.Thread.Sleep(20);
                    list.Add(new TestClass());
                    if(i%100==0)
                    { 
                        Console.WriteLine(i.ToString() +  " 个项目已添加");
                    }
                }

                //查询项目
                var newList = list.Where(o=> o.CreateDate.AddSeconds(5)> DateTime.Now);
                while (newList.Count() > 0)
                {
                    //注意 - 实际计数不断减少...显示我们的“执行”每次调用计数时都在运行。
                    Console.WriteLine(newList.Count());
                    System.Threading.Thread.Sleep(500);
                }
            }
        }
    }

2
list.Where返回一个IEnumerable,所以你不能使用Count()的快捷方式...如果你将其实现,你会看到相当不错的性能(例如:list.Where(o=> o.CreateDate.AddSeconds(5)> DateTime.Now).ToList())。 - Sam Saffron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接