Use Count in Enumerable.Any if available by stephentoub · Pull Request #40377 · dotnet/corefx (original) (raw)

We've been hesitant to make this change in the past, as it adds several interface checks which do show up in microbenchmarks (as is evidenced below).

However, wide-spread "wisdom" is that Any() is as fast or faster than Count() > 0, and there are even FxCop rules/analyzers that warn about using the latter instead of the former, but in its current form that can frequently be incorrect: if the source does implement ICollection<T>, generally its Count is O(1) and allocation-free, whereas Any() will almost always end up allocating an enumerator.

On balance, it seems better to just have Any() map closely to Count() so that their performance can be reasoned about in parallel. I'd like a second opinion, though. @cston? @ahsonkhan? @bartonjs?

using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running;

[MemoryDiagnoser] public class Program { public static void Main(string[] args) => BenchmarkSwitcher.FromTypes(new[] { typeof(Program) }).Run(args);

private static IEnumerable<int> Iterator() { yield return 1; }

public IEnumerable<object[]> Sources()
{
    yield return new object[] { "Empty", Enumerable.Empty<int>() };
    yield return new object[] { "Range", Enumerable.Range(0, 10) };
    yield return new object[] { "List", new List<int>() { 1, 2, 3 } };
    yield return new object[] { "int[]", new int[] { 1, 2, 3 } };
    yield return new object[] { "int[].Select", new int[] { 1, 2, 3 }.Select(i => i) };
    yield return new object[] { "int[].Select.Where", new int[] { 1, 2, 3 }.Select(i => i).Where(i => i % 2 == 0) };
    yield return new object[] { "Iterator", Iterator() };
    yield return new object[] { "Iterator.Select", Iterator().Select(i => i) };
    yield return new object[] { "Iterator.Select.Where", Iterator().Select(i => i).Where(i => i % 2 == 0) };
}

[Benchmark]
[ArgumentsSource(nameof(Sources))]
public void Any(string name, object source) => Unsafe.As<IEnumerable<int>>(source).Any();

}

produces:

Method Toolchain name source Mean Allocated
Any New Empty Syste(...)nt32] [42] 6.966 ns -
Any Old Empty Syste(...)nt32] [42] 5.421 ns -
Any New Iterator Progr(...)>d__1 [22] 20.192 ns 32 B
Any Old Iterator Progr(...)>d__1 [22] 13.645 ns 32 B
Any New Iterator.Select Syste(...)nt32] [76] 42.764 ns 88 B
Any Old Iterator.Select Syste(...)nt32] [76] 35.661 ns 88 B
Any New Itera(...)Where [21] Syste(...)nt32] [62] 74.852 ns 144 B
Any Old Itera(...)Where [21] Syste(...)nt32] [62] 65.916 ns 144 B
Any New List Syste(...)nt32] [47] 3.979 ns -
Any Old List Syste(...)nt32] [47] 12.500 ns 40 B
Any New Range Syste(...)rator [36] 7.972 ns -
Any Old Range Syste(...)rator [36] 15.880 ns 40 B
Any New int[] System.Int32[] 11.606 ns -
Any Old int[] System.Int32[] 9.594 ns 32 B
Any New int[].Select Syste(...)nt32] [71] 8.505 ns -
Any Old int[].Select Syste(...)nt32] [71] 19.888 ns 48 B
Any New int[].Select.Where Syste(...)nt32] [62] 59.662 ns 104 B
Any Old int[].Select.Where Syste(...)nt32] [62] 48.749 ns 104 B

ps @adamsitnik, I could not figure out how to get the benchmark to take an IEnumerable<int>; everything I tried resulted in errors like error CS0266: Cannot implicitly convert type 'object' to 'System.Collections.Generic.IEnumerable<int>'. This is with benchmarkdotnet 11.5.