Rasmus Olsson

Extend your toolkit with benchmarks

December 25, 2021

Performance issues inside applications comes in many forms and in the best of times they can be easily identified. But they can also get you real stuck and impossible to get your head around. It can be hard to know what changes to the application are needed in order for it to execute faster, in those scenarios benchmarking can be a great tool to consider. We can rely on a baseline, benchmark what we have in place today, and then work from that to validate our efforts to make it faster.

Setting up benchmarks in dotnet

To setup a benchmark we start by creating a new console application and then install the nuget package BenchmarkDotNet.

After that we need to specify what benchmarks we need to run. We can do this with the BenchmarkRunner.

public class Program { public static void Main(string[] args) { BenchmarkRunner.Run<CustomerBenchmarks>(); } }

The CustomerBenchmark class contains the code to benchmark. For simplicity, let say that we want to compare the performance of the linq .First() vs .Singel(). This is how that could look like:

public class CustomerBenchmarks { [Params(100, 200, 300, 400, 500)] public int Amount { get; set; } private Customer[] _customers = Array.Empty<Customer>(); [GlobalSetup] public void GlobalSetup() { _customers = Seed(Amount); } [Benchmark] public void FindFirstCustomerById() { _customers.First(x => x.Id == Amount/2); } [Benchmark] public void FindSingleCustomerById() { _customers.Single(x => x.Id == Amount/2); } private static Customer[] Seed(int amount) { return Enumerable.Range(0, amount).Select(i => new Customer { Id = i, Name = $"name-{i}" }).ToArray(); } }

BenchmarkDotNet have a wide variety of attributes, I used the most fundamental ones:

  • [Params] - Populates Amount with different scenarios.
  • [GlobalSetup] - Will run for every scenario.
  • [Benchmark] - The method to benchmark

How to run the benchmark

In order for the benchmark to run reliable we need to build the solution with the release flag.

dotnet build --configuration release

After that we can navigate to the release folder and execute:

sudo dotnet Customer.Performance.dll

Note that Im using sudo, this is so that BenchmarkDotnet can increase the CPU priority for the process.

And the results:

benchmark-performance-example

And that pretty much it. Maybe not so surprising, First() is faster then Single(). This is because Single() takes the responsibility of checking for duplicates (which is a great feature). One really nice thing about dotnet core is that we can locate the exact line at github. https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Single.cs#L74

Happy Coding!

please share