C# Array.Count()比List.Count()慢得多

C# Array.Count()比List.Count()慢得多,c#,.net,performance,linq,ienumerable,C#,.net,Performance,Linq,Ienumerable,使用扩展方法IEnumerable时,数组的速度至少是列表的两倍 Function Count() List<int> 2,299 int[] 6,903 编辑: 答案非常惊人,但我想补充一句。 实际上有两个等效块,只需测试 不同的集合接口类型,并使用它找到的任何一种 首先(如果有的话)。我不知道.NET实现是否测试了 ICollection或ICo

使用扩展方法
IEnumerable
时,数组的速度至少是列表的两倍

Function                      Count()
List<int>                     2,299
int[]                         6,903

编辑:

答案非常惊人,但我想补充一句。

实际上有两个等效块,只需测试 不同的集合接口类型,并使用它找到的任何一种 首先(如果有的话)。我不知道.NET实现是否测试了 ICollection或ICollection首先-我可以通过实现 两个接口,但每个接口返回的计数不同, 但这可能有点过分了。这对我来说并不重要 表现良好的集合,而不是轻微的性能差异 -我们想先测试“最有可能”的接口,我相信这是通用接口

泛型转换可能是最有可能发生的,但如果将两者颠倒,即在泛型转换之前调用非泛型转换,Array.Count()将比List.Count()快一点。另一方面,列表的非通用版本速度较慢

很高兴知道是否有人想在1e8迭代循环中调用
Count()

Function       ICollection<T> Cast     ICollection Cast
List                1,268                   1,738         
Array               5,925                   1,683
函数ICollection Cast ICollection Cast
名单12681738
阵列59251683

这是因为
int[]
需要施法,而
List
不需要。如果要使用Length属性,则结果将非常不同-大约比
List.Count()

快10倍,原因是
Enumerable.Count()
执行强制转换到
ICollection
以从列表和数组中检索计数

使用此示例代码:

public static int Count<TSource>(IEnumerable<TSource> source)
{
    ICollection<TSource> collection = source as ICollection<TSource>;
    if (collection != null)
    {
        return 1; // collection.Count;
    }
}
公共静态整数计数(IEnumerable源代码)
{
ICollection collection=源作为ICollection;
if(集合!=null)
{
返回1;//collection.Count;
}
}
您可以确定阵列的强制转换需要更长的时间,事实上,计数所花费的大部分时间都来自此强制转换:

Function                      Count()
List<int>                     1,575
int[]                         5,069
函数计数()
清单1575
int[]5069
关键可能是(我强调的)这句话:

在.NET Framework 2.0版中,Array类实现System.Collections.Generic.IList, System.Collections.Generic.ICollection和 System.Collections.Generic.IEnumerable通用接口最新版本 实现在运行时提供给阵列,因此 文档生成工具不可见。因此,通用 接口不会出现在数组的声明语法中 类,并且没有接口成员的引用主题 只能通过将数组强制转换为泛型接口类型来访问 (显式接口实现)

32位评测分析(全部以毫秒为单位,仅感兴趣的位,禁用JIT内联):


当应用程序退出时,会创建一个
报告.tab
文件,该文件可以在Excel中使用。

我发布这篇文章不是为了回答问题,而是为了提供一个更易于测试的环境

我复制了
Enumerable.Count()
的实际实现,并更改了原始测试程序以使用它,因此人们可以在调试器中单步执行它

如果您运行下面代码的发布版本,您将获得与OP类似的计时

对于
List
int[]
而言,分配给
is2
的第一次强制转换将为非空,因此将调用
is2.Count

因此,差异似乎来自
.Count
的内部实现

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        public const long Iterations = (long)1e8;

        static void Main()
        {
            var list = new List<int>() { 1 };
            var array = new int[1];
            array[0] = 1;

            var results = new Dictionary<string, TimeSpan>();
            results.Add("int[]", Benchmark(array, Iterations));
            results.Add("List<int>", Benchmark(list, Iterations));

            Console.WriteLine("Function".PadRight(30) + "Count()");
            foreach (var result in results)
            {
                Console.WriteLine("{0}{1}", result.Key.PadRight(30), Math.Round(result.Value.TotalSeconds, 3));
            }
            Console.ReadLine();
        }

        public static TimeSpan Benchmark(IEnumerable<int> source, long iterations)
        {
            var countWatch = new Stopwatch();
            countWatch.Start();
            for (long i = 0; i < iterations; i++) Count(source);
            countWatch.Stop();

            return countWatch.Elapsed;
        }

        public static int Count<TSource>(IEnumerable<TSource> source)
        {
            ICollection<TSource> is2 = source as ICollection<TSource>;

            if (is2 != null)
                return is2.Count;  // This is executed for int[] AND List<int>.

            ICollection is3 = source as ICollection;

            if (is3 != null)
                return is3.Count;

            int num = 0;

            using (IEnumerator<TSource> enumerator = source.GetEnumerator())
            {
                while (enumerator.MoveNext())
                    num++;
            }

            return num;
        }
    }
}
以上代码给出了在调试器外部运行的版本生成的以下结果:

Array elapsed = 00:00:02.9586515
List elapsed = 00:00:00.6098578
在这一点上,我想“我们当然可以优化
Count()
来识别
T[]
并直接返回
.Length
。因此我将
Count()
的实现更改如下:

public static int Count<TSource>(IEnumerable<TSource> source)
{
    var array = source as TSource[];

    if (array != null)        // Optimised for arrays.
        return array.Length;  // This is executed for int[] 

    ICollection<TSource> is2 = source as ICollection<TSource>;

    if (is2 != null)
        return is2.Count;  // This is executed for List<int>.

    ICollection is3 = source as ICollection;

    if (is3 != null)
        return is3.Count;

    int num = 0;

    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
            num++;
    }

    return num;
}
公共静态整数计数(IEnumerable源代码)
{
var数组=源作为TSource[];
if(array!=null)//针对数组进行了优化。
return array.Length;//这是对int[]执行的
ICollection is2=源作为ICollection;
如果(is2!=null)
return is2.Count;//这是针对列表执行的。
ICollection is3=源作为ICollection;
如果(is3!=null)
返回值为3。计数;
int num=0;
使用(IEnumerator enumerator=source.GetEnumerator())
{
while(枚举数.MoveNext())
num++;
}
返回num;
}
值得注意的是,即使进行了此更改,阵列在我的系统上仍然较慢,尽管非阵列必须进行额外的强制转换

我的结果(发布版本)是:

Function                      Count()
List<int>                     1.753
int[]                         2.304
函数计数()
清单1.753
int[]2.304

我完全无法解释这最后一个结果…

+1很有意思。而且,你的数字似乎是64位的。在32位中,差异要大得多!也许这有助于我的测试速度实际上慢了四倍!最有意思的是。在32位中,我大约慢了38倍
O
对不起,这不是真的。
int[]
确实实现了
ICollection
我已经完成了
Enumerable.Count()的实现,并验证了它没有执行两次强制转换。是的,
int[]
确实实现了
ICollection
但是强制转换本身很慢…将修改答案…@MatthewWatson:我想我们都得出了这样的结论,对
ICollection
的强制转换在数组的情况下是特殊处理的。我们可以得出这样的结论:ICollection是在运行时通过执行一些疯狂的操作来检索的我的评论是按照这个答案的原始形式写的,它不真实
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            int dummy = 0;
            int count = 1000000000;

            var array = new int[1] as ICollection<int>;
            var list = new List<int> {0};

            var sw = Stopwatch.StartNew();

            for (int i = 0; i < count; ++i)
                dummy += array.Count;

            Console.WriteLine("Array elapsed = " + sw.Elapsed);

            dummy = 0;
            sw.Restart();

            for (int i = 0; i < count; ++i)
                dummy += list.Count;

            Console.WriteLine("List elapsed = " + sw.Elapsed);

            Console.ReadKey(true);
        }
    }
}
Array elapsed = 00:00:02.9586515
List elapsed = 00:00:00.6098578
public static int Count<TSource>(IEnumerable<TSource> source)
{
    var array = source as TSource[];

    if (array != null)        // Optimised for arrays.
        return array.Length;  // This is executed for int[] 

    ICollection<TSource> is2 = source as ICollection<TSource>;

    if (is2 != null)
        return is2.Count;  // This is executed for List<int>.

    ICollection is3 = source as ICollection;

    if (is3 != null)
        return is3.Count;

    int num = 0;

    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
            num++;
    }

    return num;
}
Function                      Count()
List<int>                     1.753
int[]                         2.304