Array.Count() much slower than List.Count()

前端 未结 4 1882
梦如初夏
梦如初夏 2021-02-01 18:00

When using the extension method of IEnumerable Count(), an array is at least two times slower than a list.

Function                      Co         


        
4条回答
  •  佛祖请我去吃肉
    2021-02-01 18:40

    I'm posting this, not as an answer, but to provide a more testable environment.

    I have taken a copy of the actual implementation of Enumerable.Count() and changed the original test program to use it, so people can single-step it in the debugger.

    If you run a release version of the code below, you will get similar timings to the OP.

    For both List and int[] the first cast assigned to is2 will be non-null so is2.Count will be called.

    So it would appear the difference is coming from the internal implementation of .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() { 1 };
                var array = new int[1];
                array[0] = 1;
    
                var results = new Dictionary();
                results.Add("int[]", Benchmark(array, Iterations));
                results.Add("List", 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 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(IEnumerable source)
            {
                ICollection is2 = source as ICollection;
    
                if (is2 != null)
                    return is2.Count;  // This is executed for int[] AND List.
    
                ICollection is3 = source as ICollection;
    
                if (is3 != null)
                    return is3.Count;
    
                int num = 0;
    
                using (IEnumerator enumerator = source.GetEnumerator())
                {
                    while (enumerator.MoveNext())
                        num++;
                }
    
                return num;
            }
        }
    }
    

    Given this information, we can simplify the test to just concentrate on the timing differences between List.Count and Array.Count:

    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;
                var list = new List {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);
            }
        }
    }
    

    The above code gives the following results for a release build run outside the debugger:

    Array elapsed = 00:00:02.9586515
    List elapsed = 00:00:00.6098578
    

    At this point, I thought to myself "surely we can optimise the Count() to recognise T[] and return .Length directly. So I changed the implementation of Count() as follows:

    public static int Count(IEnumerable source)
    {
        var array = source as TSource[];
    
        if (array != null)        // Optimised for arrays.
            return array.Length;  // This is executed for int[] 
    
        ICollection is2 = source as ICollection;
    
        if (is2 != null)
            return is2.Count;  // This is executed for List.
    
        ICollection is3 = source as ICollection;
    
        if (is3 != null)
            return is3.Count;
    
        int num = 0;
    
        using (IEnumerator enumerator = source.GetEnumerator())
        {
            while (enumerator.MoveNext())
                num++;
        }
    
        return num;
    }
    

    Remarkably, even after making this change, the arrays were still slower on my system, despite the non-arrays having to make the extra cast!

    Results (release build) for me were:

    Function                      Count()
    List                     1.753
    int[]                         2.304
    

    I'm at a total loss to explain this last result...

提交回复
热议问题