Vimal Stan Vimal Stan - 3 months ago 18
C# Question

C#: Any() vs Count() for an empty list

A question posted earlier got me thinking. Would

Any()
and
Count()
perform similarly when used on an empty list?

As explained here, both should go through the same steps of
GetEnumerator()/MoveNext()/Dispose()
.

I tested this out using quick program on LINQPad:

static void Main()
{
var list = new List<int>();

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

for (int i = 0; i < 10000; i++)
list.Any();

stopwatch.Stop();
Console.WriteLine("Time elapsed for Any() : {0}", stopwatch.Elapsed);


stopwatch = new Stopwatch();
stopwatch.Start();

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

stopwatch.Stop();
Console.WriteLine("Time elapsed for Count(): {0}", stopwatch.Elapsed);
}


And the general result seems to indicate that
Count()
is faster in this situation. Why is that?

I'm not sure if I got the benchmark right, I would appreciate any correction if not.




Edit: I understand that it would make more sense semantically. The first link I've posted in the question shows a situation where it does make sense to do use
Count()
directly since the value would be used, hence the question.

Answer

The Count() method is optimized for ICollection<T> type, so the pattern GetEnumerator()/MoveNext()/Dispose() is not used.

list.Count();

Is translated to

((ICollection)list).Count;

Whereas the Any() has to build an enumerator. So the Count() method is faster.

Here a benchmarks for 4 differents IEnumerable instance. The MyEmpty looks like IEnumerable<T> MyEmpty<T>() { yield break; }

iterations : 100000000

Function                      Any()     Count()
new List<int>()               4.310     2.252
Enumerable.Empty<int>()       3.623     6.975
new int[0]                    3.960     7.036
MyEmpty<int>()                5.631     7.194

As casperOne said in the comment, Enumerable.Empty<int>() is ICollection<int>, because it is an array, and arrays are not good with the Count() extension because the cast to ICollection<int> is not trivial.

Anyway, for a homemade empty IEnumerable, we can see what we expected, that Count() is slower than Any(), due to the overhead of testing if the IEnumerable is a ICollection.

Complete benchmark:

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

    static void Main()
    {
        var results = new Dictionary<string, Tuple<TimeSpan, TimeSpan>>();
        results.Add("new List<int>()", Benchmark(new List<int>(), Iterations));
        results.Add("Enumerable.Empty<int>()", Benchmark(Enumerable.Empty<int>(), Iterations));
        results.Add("new int[0]", Benchmark(new int[0], Iterations));
        results.Add("MyEmpty<int>()", Benchmark(MyEmpty<int>(), Iterations));

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

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

        var countWatch = new Stopwatch();
        countWatch.Start();
        for (long i = 0; i < iterations; i++) source.Count();
        countWatch.Stop();

        return new Tuple<TimeSpan, TimeSpan>(anyWatch.Elapsed, countWatch.Elapsed);
    }

    public static IEnumerable<T> MyEmpty<T>() { yield break; }
}
Comments