ZetCode

C# benchmark

last modified September 14, 2022

In this article we benchmark C# code with BenchmarkDotNet library.

Benchmarking is the process of measuring the performance of our code. It allow us to determine performance bottlenecks in our programs.

BenchmarkDotNet is a powerful .NET library for performing benchmarks. We can measure C#, F#, and VB code.

$ dotnet add package BenchmarkDotNet

We install the BenchmarkDotNet package.

$ dotnet run --project SimpleEx.csproj -c Release

This is how we run our benchmark.

C# benchmark simple example

In the following example, we measure the performance of various ways of string concatenation.

Program.cs
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
public class Program
{
    int n = 10_000;

    [Benchmark]
    public string Builder()
    {
        StringBuilder output = new StringBuilder();

        for (int i = 0; i < n; i++)
        {
            output.Append("falcon").Append(i);
        }

        return output.ToString();
    }

    [Benchmark]
    public string Interpolation()
    {
        string output = string.Empty;

        for (int i = 0; i < n; i++)
        {
            output = $"{output}falcon{i}";
        }

        return output;
    }

    [Benchmark]
    public string Addition()
    {
        string output = string.Empty;

        for (int i = 0; i < n; i++)
        {
            output += "falcon" + i;
        }

        return output.ToString();
    }

    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<Program>();
    }
}

In the program, we compare three methods of string concatenation: using StringBuilder, using interpolation, and using the addition operation.

[MemoryDiagnoser]
public class Program
{

Wit [MemoryDiagnoser], we also measure the memory usage.

[Benchmark]
public string Builder()
{
    StringBuilder output = new StringBuilder();

    for (int i = 0; i < n; i++)
    {
        output.Append("falcon").Append(i);
    }

    return output.ToString();
}

This method uses the StringBuilder to add strings. The method is decorated with [Benchmark].

var summary = BenchmarkRunner.Run<Program>();

We run the benchmark.

// * Summary *

BenchmarkDotNet=v0.13.2, OS=ubuntu 22.04
11th Gen Intel Core i5-1135G7 2.40GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.104
  [Host]     : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT AVX2


|        Method |         Mean |     Error |    StdDev |        Gen0 |        Gen1 |        Gen2 |    Allocated |
|-------------- |-------------:|----------:|----------:|------------:|------------:|------------:|-------------:|
|       Builder |     120.6 us |   0.30 us |   0.27 us |     62.3779 |     62.3779 |     62.3779 |    398.29 KB |
| Interpolation | 120,826.4 us | 354.51 us | 331.61 us | 290600.0000 | 247600.0000 | 247600.0000 | 956382.51 KB |
|      Addition |  73,354.2 us | 448.96 us | 419.96 us | 290714.2857 | 249000.0000 | 247714.2857 |  956694.4 KB |

The output includes OS and hardware summary and a table showing benchark statistics. From the output we can see that the addition was the fastest while builder was the most memory efficient.

C# benchmark sorting algorithms

In the next example, we benchmark sorting algorithms.

Program.cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
public class Program
{
    const int n = 100_000;
    int[] vals = new int[n];

    [GlobalSetup]
    public void GlobalSetup()
    {
        var rnd = new Random();

        for (int i = 0; i < n; i++)
        {
            vals[i] = rnd.Next(1, 100);
        }
    }

    [Benchmark]
    public void SelectionSort()
    {
        int len = vals.Length;

        for (int i = 0; i < len - 1; i++)
        {
            int min_idx = i;

            for (int j = i + 1; j < len; j++)
            {
                if (vals[j] < vals[min_idx])
                {
                    min_idx = j;
                }
            }

            int temp = vals[min_idx];
            vals[min_idx] = vals[i];
            vals[i] = temp;
        }
    }

    [Benchmark]
    public void BubbleSort()
    {
        int len = vals.Length;

        for (int i = 0; i < len - 1; i++)
        {
            for (int j = 0; j < len - i - 1; j++)
            {
                if (vals[j] > vals[j + 1])
                {
                    int temp = vals[j];
                    vals[j] = vals[j + 1];
                    vals[j + 1] = temp;
                }
            }
        }
    }

    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<Program>();
    }
}

We compare selection sort with the bubble sort algorithm.

const int n = 100_000;
int[] vals = new int[n];

[GlobalSetup]
public void GlobalSetup()
{
    var rnd = new Random();

    for (int i = 0; i < n; i++)
    {
        vals[i] = rnd.Next(1, n);
    }
}

Using [GlobalSetup] attribute, we prepare an array of 100000 randomly chosen integer values between 1 and 100000. This code is executed only once.

[Benchmark]
public void SelectionSort()
{
    int len = vals.Length;

    for (int i = 0; i < len - 1; i++)
    {
        int min_idx = i;

        for (int j = i + 1; j < len; j++)
        {
            if (vals[j] < vals[min_idx])
            {
                min_idx = j;
            }
        }

        int temp = vals[min_idx];
        vals[min_idx] = vals[i];
        vals[i] = temp;
    }
}

We have the selection sort algorithm; it sorts the prepared array of integers.

|        Method |    Mean |    Error |   StdDev | Allocated |
|-------------- |--------:|---------:|---------:|----------:|
| SelectionSort | 3.646 s | 0.0569 s | 0.0532 s |   1.38 KB |
|    BubbleSort | 4.124 s | 0.0136 s | 0.0127 s |   3.28 KB |

The selection sort is slightly better both in terms of memory and speed.

In this article we have measured the performance of our C# code with BenchmarkDotNet library.

List all C# tutorials.