C# benchmark
last modified October 5, 2026
In this article we benchmark C# code with the BenchmarkDotNet library.
Benchmarking measures how long code takes to run and how much memory it allocates. It is useful for comparing alternatives, finding regressions, and checking whether an optimization helps on a representative workload.
BenchmarkDotNet is a .NET library that runs methods many times, performs warmups, and reports statistics such as the mean execution time and allocated memory. Always run benchmarks in Release mode and treat the numbers as machine-dependent measurements.
$ dotnet new console -n BenchmarkSample $ cd BenchmarkSample $ dotnet add package BenchmarkDotNet $ dotnet run -c Release
The commands create a console project, add BenchmarkDotNet, and run the
benchmark. Replace the generated Program.cs with one of the
examples below.
C# string benchmark
The following example compares a StringBuilder with repeated
string concatenation. The benchmark methods return the same result so the
comparison measures the construction work rather than console output.
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class StringBenchmarks
{
private const int Count = 10_000;
[Benchmark(Baseline = true)]
public string StringBuilder()
{
var output = new StringBuilder();
for (int i = 0; i < Count; i++)
{
output.Append("falcon").Append(i);
}
return output.ToString();
}
[Benchmark]
public string Concatenation()
{
var output = string.Empty;
for (int i = 0; i < Count; i++)
{
output += $"falcon{i}";
}
return output;
}
}
public class Program
{
public static void Main()
{
BenchmarkRunner.Run<StringBenchmarks>();
}
}
[Benchmark] marks methods that BenchmarkDotNet should measure.
The Baseline value makes the first method the reference for the
ratio column. [MemoryDiagnoser] adds allocation information to the
summary. The runner should be called from a Release build, not from a debugger.
// * Summary * | Method | Mean | Ratio | Allocated | |----------------- |----------:|------:|----------:| | StringBuilder | 120.0 us | 1.00 | 398 KB | | Concatenation | 1,500 us | 12.50 | 4,000 KB |
The displayed values are representative rather than universal: processor,
.NET version, runtime settings, and BenchmarkDotNet version affect every
result. In this example, StringBuilder is faster and allocates less
memory for the repeated construction.
Benchmarking sorting algorithms
Benchmark setup should not accidentally become part of the measured operation.
Here [GlobalSetup] creates one reproducible input. Each benchmark
method clones that input before sorting, so both algorithms sort the same
values on every invocation.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class SortBenchmarks
{
private const int Count = 10_000;
private int[] original = null!;
[GlobalSetup]
public void GlobalSetup()
{
original = new int[Count];
var random = new Random(1234);
for (int i = 0; i < original.Length; i++)
{
original[i] = random.Next();
}
}
[Benchmark(Baseline = true)]
public void ArraySort()
{
var values = (int[]) original.Clone();
Array.Sort(values);
}
[Benchmark]
public void SelectionSort()
{
var values = (int[]) original.Clone();
for (int i = 0; i < values.Length - 1; i++)
{
int minimum = i;
for (int j = i + 1; j < values.Length; j++)
{
if (values[j] < values[minimum])
{
minimum = j;
}
}
(values[i], values[minimum]) = (values[minimum], values[i]);
}
}
}
public class Program
{
public static void Main()
{
BenchmarkRunner.Run<SortBenchmarks>();
}
}
Array.Sort is the baseline because it is a practical library
implementation. Selection sort is included for comparison; its quadratic time
complexity makes it unsuitable for large arrays. The clone is part of each
measured method, but it is the same operation for both algorithms, and it
ensures that every invocation starts with unsorted data.
| Method | Mean | Ratio | Allocated | |-------------- |-----------:|------:|----------:| | ArraySort | 70.0 us | 1.00 | 39.1 KB | | SelectionSort | 45.00 ms | 642.86 | 39.1 KB |
These output values are illustrative. Run the program on the target machine and compare repeated runs; a benchmark result is not a promise about every computer or workload.
Benchmarking guidelines
Use realistic input sizes, avoid I/O inside benchmark methods, keep setup work out of the measured method, and benchmark in a quiet environment. Check the result for correctness so an optimization cannot succeed by doing less work. BenchmarkDotNet's warnings and generated reports are useful when diagnosing unstable measurements.
Source
BenchmarkDotNet - getting started
BenchmarkDotNet - GitHub repository
In this article we used BenchmarkDotNet to compare C# implementations and to interpret timing and memory results.
Author
List all C# tutorials.