csharp-benchmark-net

How to perform performance tests in C# with BenchmarkDotNet

  • 3 min

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);
}
Copied!

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.

MethodRuntimeNMeanErrorStdDevRatio
Sha256.NET 4.7.210007.735 us0.1913 us0.4034 us1.00
Sha256.NET Core 3.010003.989 us0.0796 us0.0745 us0.50
Md5.NET 4.7.210002.872 us0.0552 us0.0737 us1.00
Md5.NET Core 3.010001.848 us0.0348 us0.0326 us0.64
Sha256.NET 4.7.21000074.509 us1.5787 us4.6052 us1.00
Sha256.NET Core 3.01000036.049 us0.7151 us1.0025 us0.49
Md5.NET 4.7.21000017.308 us0.3361 us0.4250 us1.00
Md5.NET Core 3.01000015.726 us0.2064 us0.1930 us0.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.