Skip to content

Instantly share code, notes, and snippets.

@Adam--
Last active May 25, 2021 16:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Adam--/1f45499bfb256ec00b1f5fcda0cfa139 to your computer and use it in GitHub Desktop.
Save Adam--/1f45499bfb256ec00b1f5fcda0cfa139 to your computer and use it in GitHub Desktop.
I often see `.Count() > 0` or `.Count > 0` to check if there are any elements in some collection. Linq provides an `Any()` method to check if there are any elements. `Any()` sure reads better but does it come with any downsides?

Linq Any() performance compared to Count() > 0

I often see .Count() > 0 or .Count > 0 to check if there are any elements in some collection. Linq provides an Any() method to check if there are any elements. Any() sure reads better but does it come with any downsides? Inspired by a twitter post on using Any() and benchmarking it, I decided to run some of my own tests using BenchmarkDotNet.

The benchmarks that were run compare the use of these two methods, comparing enumerables and lists with two sizes, 1000 and 1000.

BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.985 (20H2/October2020Update)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.203
  [Host]     : .NET 5.0.6 (5.0.621.22011), X64 RyuJIT
  DefaultJob : .NET 5.0.6 (5.0.621.22011), X64 RyuJIT

Method N Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
Enumerable_Any 1000 12.7297 ns 0.2874 ns 0.2400 ns 12.7911 ns - - - -
Enumerable_CountGreaterThanZero 1000 83,476.7798 ns 1,015.1761 ns 949.5963 ns 83,264.1113 ns - - - -
List_Any 1000 5.6846 ns 0.1155 ns 0.1080 ns 5.6549 ns - - - -
List_CountGreaterThanZero 1000 0.0248 ns 0.0268 ns 0.0358 ns 0.0000 ns - - - -
Enumerable_Any 10000 12.5423 ns 0.1560 ns 0.1382 ns 12.5044 ns - - - -
Enumerable_CountGreaterThanZero 10000 835,227.9053 ns 5,795.2965 ns 4,524.5863 ns 834,107.7637 ns - - - -
List_Any 10000 5.5781 ns 0.1236 ns 0.1423 ns 5.5443 ns - - - -
List_CountGreaterThanZero 10000 0.0106 ns 0.0153 ns 0.0143 ns 0.0000 ns - - - -

Enumerable performance

It's pretty safe to say that Count() > 0 on an enumerable is pretty slow, but why is that? Enumerable.Count() needs to iterate over each element and count them. Let's first take a look at Any().

public static bool Any<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);

    if (source is ICollection<TSource> collectionoft) return collectionoft.Count != 0;
    else if (source is IIListProvider<TSource> listProv)
    {
        // Note that this check differs from the corresponding check in
        // Count (whereas otherwise this method parallels it).  If the count
        // can't be retrieved cheaply, that likely means we'd need to iterate
        // through the entire sequence in order to get the count, and in that
        // case, we'll generally be better off falling through to the logic
        // below that only enumerates at most a single element.
        int count = listProv.GetCount(onlyIfCheap: true);
        if (count >= 0) return count != 0; 
    }
    else if (source is ICollection collection) return collection.Count != 0;

    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        return e.MoveNext();
    }
}

Here we can see that there are a lot of conditionals to find the fastest way to determine if there are any elements. The last bit of code is what gets executed in our benchmark. There is a single iteration e.MoveNext() and a Boolean return. This doesn't depend on the size, it's O(1). Lets now take a look at Count().

public static int Count<TSource>(this IEnumerable<TSource> source) {
    if (source == null) throw Error.ArgumentNull("source");
    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) return collectionoft.Count;
    ICollection collection = source as ICollection;
    if (collection != null) return collection.Count;
    
    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        checked {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

Here we see that the iteration is called for every element while (e.MoveNext()), a counter incremented, and the count returned. Looking back at the benchmark data and comparing Enumerable_CountGreaterThanZero with N = 1,000 and N = 10,000 the runtime is a direct function of the size, it's O(n).

So if you want to know if there are any elements in an Enumerable, use Any(). If you use Resharper, it will provide a hint to use Any() instead of Count() > 0

alt text

List performance

When it comes to lists, calling count Count is near instantaneous. Any() takes longer, however, it's still fast. Both are O(1).

The Any() method, is the same method that is called on the enumerable since list implements IEnumerable. Referring to the code for Any(), we can see that it finds the fastest way to check if there are any elements. Since List is an IColleciton, it executes .Count != 0 for this check.

Notice that Count isn't a method, it's a property. In lists, it doesn't need to iterate over every element to determine how many there are of them, but instead lists keep track of how many elements there are and it returns that integer.

// Read-only property describing how many elements are in the List.
public int Count => _size;

The performance difference between Any() and Count > 0 is the type checking overhead in Any(). The performance is almost negligible however and it is always advised to avoid premature optimization and to prefer legibility. So unless you are running this in a hot path, calling it very heavily, Any() would be preferred.

Benchmark Source Code

The benchmark source code used for this comparison is included in it's entirety here.

LinqAnyBenchmark.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
  </ItemGroup>

</Project>

Benchmarks.cs

namespace LinqAnyBenchmark
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using BenchmarkDotNet.Attributes;

    [MemoryDiagnoser]

    public class Benchmark
    {
        private IEnumerable<Guid> enumerableGuids;
        private List<Guid> listGuids;

        [Params(1000, 10000)]
        public int N;

        [GlobalSetup]
        public void Setup()
        {
            enumerableGuids =
                Enumerable
                    .Range(0, N)
                    .Select(_ => Guid.NewGuid());
            listGuids =
                Enumerable
                    .Range(0, N)
                    .Select(_ => Guid.NewGuid())
                    .ToList();
        }

        [Benchmark]
        public bool Enumerable_Any() => enumerableGuids.Any();

        [Benchmark]
        public bool Enumerable_CountGreaterThanZero() => enumerableGuids.Count() > 0;

        [Benchmark]
        public bool List_Any() => listGuids.Any();

        [Benchmark]
        public bool List_CountGreaterThanZero() => listGuids.Count > 0;
    }
}

Program.cs

namespace LinqAnyBenchmark
{
    using BenchmarkDotNet.Running;

    class Program
    {
        static void Main(string[] args) => BenchmarkRunner.Run<Benchmark>();
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment