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.