Boost Your App’s Performance with .NET Benchmarking

In this post, we’re exploring a tool every .NET developer should have in their toolkit: BenchmarkDotNet. Whether you’re optimizing existing code or writing performance-critical applications, measuring execution time and improving efficiency is crucial. Let’s explore BenchmarkDotNet and how to use it to level up your .NET performance game.

What is BenchmarkDotNet?

BenchmarkDotNet is an open-source benchmarking library for .NET applications. It helps you measure your methods’ performance by running them multiple times, collecting statistical data, and providing detailed reports on execution time, memory usage, and more. The library is powerful yet simple to integrate into existing projects.

Getting Started

To get started, install the BenchmarkDotNet NuGet package:

Install-Package BenchmarkDotNet


Or:

dotnet add package BenchmarkDotNet


Once installed, you only need a basic setup to benchmark your code. Here’s a simple example to illustrate:

public class StringBenchmarks
{
    private const int Count = 1000;

    [Benchmark]
    public string StringConcatenation()
    {
        string result = string.Empty;
        for (int i = 0; i < Count; i++)
            result += "Hello World!";
        return result;
    }

    [Benchmark]
    public string StringBuilder()
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < Count; i++)
            sb.Append("Hello World!");
        return sb.ToString();
    }
}


Let’s break down the example above. 

To create a benchmark, you must create a public class with public methods and decorate the benchmarking methods with the Benchmark attribute.

In the example, two methods do the same: create an empty string and add “Hello World!” 1,000 times in the loop. The first method uses classic string concatenation, and the second method uses the StringBuilder type.

The BenchmarkRunner runs your benchmarks. You must run your app in the Release mode. Ideally, close your IDE (and other applications) and use CLI:

dotnet run -c Release


In the console, you’ll get the following result:

By default, you see four columns: Method, Mean, Error, and StdDev.

The Method column is prominent; it’s the name of the benchmark. 

The Mean column shows the average execution time for the benchmarked method across all iterations.

The Error column represents the margin of error in the mean result, indicating how accurate the mean is.

The Standard Deviation (StdDev) column shows the degree of variation in the execution times across different runs.

Baseline

You can mark a method as a baseline:

[Benchmark(Baseline = true)]
public string StringBuilder()


It will add a Ratio column to the summary result:

The Ratio column shows the performance of the benchmarked method relative to the baseline method.

In our example, the StringBuilder is faster than string concatenation by almost 150 times. 

The RatioSD column shows the standard deviation of the ratio, indicating how much the ratio varies across different runs.

Hide Columns

You can hide columns from summary results with a [HideColumns] attribute: 

[HideColumns("Error", "StdDev", "RatioSD")]
public class StringBenchmarks


The result:

Memory Diagnoser 

BenchmarkDotNet can also analyze memory usage. You have to add the [MemoryDiagnoser] attribute to your class. 

[MemoryDiagnoser]
public class StringBenchmarks


The result:

Parameterization 

The [Params] attribute in BenchmarkDotNet specifies multiple input values for a benchmark method. It allows you to run the same benchmark with different values for a given parameter, making it easy to test how performance changes with varying inputs. This is especially useful when you want to see how your method performs with different data sizes, configurations, or workloads.

[Params(100, 1_000, 10_000)]
public int Count { get; set; }


The result:

Multiple .NET Versions 

You can run your benchmarks for multiple .NET versions with a [SimpleJob] attribute and a RuntimeMoniker option. 

[SimpleJob(RuntimeMoniker.Net70)]
[SimpleJob(RuntimeMoniker.Net80)]
public class StringBenchmarks


You must install all .NET versions you want to run benchmarks and define them in the project settings. 

<PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net7.0;net8.0</TargetFrameworks>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
</PropertyGroup>


The result:

Summary

This is an intro to the BenchmarkDotNet tool. I showed the most used features by me.

However, the BenchmarkDotNet has many more features. I encourage you to explore the tool. 

Benchmarking is an essential skill for any .NET developer. BenchmarkDotNet makes setting up and gathering performance data to guide your optimization efforts easy.

You can find the source on my GitHub

Scroll to Top