Posts
Pipenv And Azure Private Package Indexes
Integrating Pipenv and Azure Private Package Indexes
Windows Terminal setup
Windows Terminal
Windows Terminal is a modern feature-rich open source terminal from Microsoft that runs many shells, including PowerShell. The easiest way to install it is from the Microsoft Store or via Chocolately with the command
choco install microsoft-windows-terminal
.Running unit tests automatically before pushing
Something that I have been using lately is a git pre-push hook to build and run our unit test suite before pushing. I like that I can ensure that I do not push any commits that break the build or unit tests and that I get fast feedback.
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 anAny()
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 usingAny()
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 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 atAny()
.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 atCount()
.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 useAny()
instead ofCount() > 0
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 forAny()
, we can see that it finds the fastest way to check if there are any elements. SinceList
is anIColleciton
, 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()
andCount > 0
is the type checking overhead inAny()
. 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>(); } }
subscribe via RSS