BenchmarkDotNet is a library for running performance tests on our code in .NET.
The performance and speed of our code is an important parameter we must consider and which, at times, is the main point of interest. To measure performance, the normal thing is to run a test to check its speed, or what we call a benchmark.
However, doing a benchmark correctly is more complicated than is usually thought, because both the development environment and the compiler will do all sorts of things that can interfere with the measurement of the result.
In fact, if you don’t use some kind of tool (and this is the best one I know for .NET) the results of your performance tests will not be reliable.
This is where BenchmarkDotNet comes into play, taking care of precisely this. It runs a series of tests so that the results are comparable, repeatable, and accurate.
Other of its main features are,
- Simple syntax and usage
- Support for multiple scenarios
- Integration with analysis tools
How to use BenchmarkDotNet
First, we add BenchmarkDotNet to our project via the corresponding NuGet package.
Install-Package BenchmarkDotNet
Next, we have an example, taken from the library’s documentation.
[SimpleJob(RuntimeMoniker.Net472, baseline: true)]
[SimpleJob(RuntimeMoniker.NetCoreApp30)]
[RPlotExporter]
public class Md5VsSha256
{
private SHA256 sha256 = SHA256.Create();
private MD5 md5 = MD5.Create();
private byte[] data;
[Params(1000, 10000)]
public int N;
[GlobalSetup]
public void Setup()
{
data = new byte[N];
new Random(42).NextBytes(data);
}
[Benchmark]
public byte[] Sha256() => sha256.ComputeHash(data);
[Benchmark]
public byte[] Md5() => md5.ComputeHash(data);
}
In this example, we have two functions we want to test, sha256.ComputeHash(data) and md5.ComputeHash(data);
First, we use the SimpleJob attributes to define the benchmarks for two platforms, Net472 and NetCore3.
On the other hand, we have the parameter N which we define can be 1000 or 10000.
Finally, we mark the GlobalSetup function, which will run at the start of each benchmark, and the two Benchmark cases, which are the functions we mentioned earlier.
If we run the code, BenchmarkDotNet provides us with the following table.
| Method | Runtime | N | Mean | Error | StdDev | Ratio |
|---|---|---|---|---|---|---|
| Sha256 | .NET 4.7.2 | 1000 | 7.735 us | 0.1913 us | 0.4034 us | 1.00 |
| Sha256 | .NET Core 3.0 | 1000 | 3.989 us | 0.0796 us | 0.0745 us | 0.50 |
| Md5 | .NET 4.7.2 | 1000 | 2.872 us | 0.0552 us | 0.0737 us | 1.00 |
| Md5 | .NET Core 3.0 | 1000 | 1.848 us | 0.0348 us | 0.0326 us | 0.64 |
| Sha256 | .NET 4.7.2 | 10000 | 74.509 us | 1.5787 us | 4.6052 us | 1.00 |
| Sha256 | .NET Core 3.0 | 10000 | 36.049 us | 0.7151 us | 1.0025 us | 0.49 |
| Md5 | .NET 4.7.2 | 10000 | 17.308 us | 0.3361 us | 0.4250 us | 1.00 |
| Md5 | .NET Core 3.0 | 10000 | 15.726 us | 0.2064 us | 0.1930 us | 0.90 |
As we can see, defining Benchmarks is very simple and, more importantly, we can run them knowing that no “weird things” will happen to trick us in the results.
Of course, the library has many more options, including generating tables and graphs of the results. If you are interested, I advise you to consult the project’s documentation.
BenchmarkDotNet is Open Source, and the documentation and all the code are available at this link https://github.com/dotnet/BenchmarkDotNet.

