Skip to content

BenchmarkDotNet

TestingC#

What it is

BenchmarkDotNet is a .NET benchmarking library that handles warm-up, statistics, memory measurement and multi-runtime comparison automatically.

Mark methods with [Benchmark]. The runner isolates each in its own process, warms up, runs many iterations, and reports statistics with allocation counts.

Installation

dotnet add package BenchmarkDotNet

Getting started

The smallest useful thing you can do with it, and what each part means.

Comparing implementations
[MemoryDiagnoser]   // reports allocations, often the real story
[SimpleJob(RuntimeMoniker.Net80)]
public class StringBenchmarks
{
    private readonly string[] _parts = Enumerable.Range(0, 100)
        .Select(i => i.ToString()).ToArray();

    [Benchmark(Baseline = true)]
    public string Concat()
    {
        var result = "";
        foreach (var p in _parts) result += p;   // O(n^2) allocations
        return result;
    }

    [Benchmark]
    public string Builder()
    {
        var sb = new StringBuilder();
        foreach (var p in _parts) sb.Append(p);
        return sb.ToString();
    }

    [Benchmark]
    public string Join() => string.Concat(_parts);
}

BenchmarkRunner.Run<StringBenchmarks>();
MemoryDiagnoser is the setting to always enable. In managed code the allocation count usually explains the timing difference better than the timing itself.

Advanced usage

Where the library earns its place over a simpler alternative.

Parameters and setup
public class SortBenchmarks
{
    [Params(100, 10_000, 1_000_000)]
    public int Size;

    private int[] _data = default!;

    [GlobalSetup]
    public void Setup() => _data = new Random(42).GenerateArray(Size);

    [IterationSetup]   // runs before each iteration, excluded from timing
    public void Reset() => _data.Shuffle(42);

    [Benchmark]
    public void ArraySort() => Array.Sort(_data);
}
IterationSetup matters when the operation mutates its input — sorting an already-sorted array measures something entirely different from sorting a random one.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Assembly was built in Debug mode
Run with -c Release. Debug disables optimisations and the numbers would be meaningless.
Results vary hugely between runs
Background load or a benchmark that mutates shared state. Use IterationSetup to restore state and close other work.

Best practices

  • Always run in Release configuration; the runner refuses Debug for good reason.
  • Enable MemoryDiagnoser — allocations usually explain the result.
  • Mark one method as Baseline so the report shows relative ratios.
  • Return a value from the benchmark so the work cannot be optimised away.

Background

Why it exists, and what it was reacting to.

It became the standard because naive Stopwatch timing is nearly always wrong — JIT warm-up, tiered compilation and garbage collection all distort results in ways this library controls for.