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

Cyr*_*don 24 .net c# linq performance ienumerable

使用扩展方法时IEnumerable<T> Count(),数组至少比列表慢两倍.

Function                      Count()
List<int>                     2,299
int[]                         6,903
Run Code Online (Sandbox Code Playgroud)

差异来自哪里?

据我所知,两者都调用Count的属性ICollection:

如果源的类型实现ICollection,则该实现用于获取元素的数量.否则,此方法确定计数.

对于它返回的列表List<T>.Count,对于数组,Array.Length.而且,Array.Length应该比...更快List<T>.Count.

基准测试:

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("List<int>", Benchmark(list, Iterations));
        results.Add("int[]", Benchmark(array, 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++) source.Count();
        countWatch.Stop();

        return countWatch.Elapsed;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

leppieKnaģis的答案非常惊人,但我想补充一句话.
正如Jon Skeet所说:

实际上有两个等效的块,只是测试不同的集合接口类型,并使用它首先找到的任何一个(如果有的话).我不知道.NET实现是否首先测试ICollection或ICollection <T> - 我可以通过实现两个接口来测试它,但当然可以从每个接口返回不同的计数,但这可能是过度的.除了轻微的性能差异之外,对于性能良好的集合并不重要 - 我们希望首先测试"最可能"的接口,我认为这是通用接口.

通用的可能是最有可能发生的,但是如果你反转这两个,即在通用的之前调用非泛型强制转换,Array.Count()变得比List.Count()快一点.另一方面,List的非通用版本较慢.

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

Function       ICollection<T> Cast     ICollection Cast
List                1,268                   1,738         
Array               5,925                   1,683
Run Code Online (Sandbox Code Playgroud)

Kna*_*ģis 9

原因是Enumerable.Count<T>()执行强制转换ICollection<T>以从列表和数组中检索计数.

使用此示例代码:

public static int Count<TSource>(IEnumerable<TSource> source)
{
    ICollection<TSource> collection = source as ICollection<TSource>;
    if (collection != null)
    {
        return 1; // collection.Count;
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以确定演员阵容需要花费更长的时间,实际上大部分时间用于计算:

Function                      Count()
List<int>                     1,575
int[]                         5,069
Run Code Online (Sandbox Code Playgroud)

关键可能是文档中的这个陈述(强调我的):

在.NET Framework 2.0版中,Array类实现System.Collections.Generic.IList,System.Collections.Generic.ICollection和System.Collections.Generic.IEnumerable通用接口.这些实现在运行时提供给数组,因此文档构建工具不可见.因此,通用接口不会出现在Array类的声明语法中,并且没有可通过将数组转换为通用接口类型(显式接口实现)来访问的接口成员的参考主题.

  • 对不起,但事实并非如此.`int []`肯定*DOES*实现`ICollection <int>`我已经单步执行`Enumerable <T> .Count()`并验证它没有进行两次强制转换. (3认同)
  • 我们可以得出结论,ICollection <T>是在运行时通过做一些比简单的接口实例强制转换而花费更多时间的疯狂东西来检索的. (2认同)

lep*_*pie 5

32位分析分析(全部以ms为单位,仅有趣的位,JIT内联禁用):

Name    Count   'Inc Time'  'Ex Time'   'Avg Inc Time'  'Avg Ex Time'
System.Linq.Enumerable::Count(<UNKNOWN>):int32 <System.Int32>   
        20000000    13338.38    7830.49 0.0007  0.0004
System.SZArrayHelper::get_Count():int32 <System.Int32>  
        10000000    4063.9      2651.44 0.0004  0.0003
System.Collections.Generic.List<System.Int32>::get_Count():int32    
        10000000    1443.99     1443.99 0.0001  0.0001
System.Runtime.CompilerServices.JitHelpers::UnsafeCast(Object):System.__Canon <System.__Canon>  
        10000004    1412.46     1412.46 0.0001  0.0001
Run Code Online (Sandbox Code Playgroud)

System.SZArrayHelper::get_Count()似乎要求System.Runtime.CompilerServices.JitHelpers::UnsafeCast数组的情况.

对于列表,List<int>.Count只需返回大小.

Inc time费用包括儿童电话费. Ex time只是方法体的成本.

禁用内联时,Array.Count()速度是两倍.

这可能是因为提到现在已删除的答案.看起来应用的属性(ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)SecuritySafeCritical)会阻止运行时内联调用,因此差异很大(在32位模式下,我的情况要慢38倍).

要自己分析一下:

获取https://github.com/leppie/IronScheme/raw/master/IronScheme/tools/IronScheme.Profiler.x86.dll 运行应用程序(仅限x86 build):

regsvr32 IronScheme.Profiler.x86.dll
set COR_PROFILER={9E2B38F2-7355-4C61-A54F-434B7AC266C0}
set COR_ENABLE_PROFILING=1
ConsoleApp1.exe
Run Code Online (Sandbox Code Playgroud)

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