When using the extension method of IEnumerable
Count(), an array is at least two times slower than a list.
Function Co
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
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...