Language: EN

csharp-benchmark-net

How to perform performance tests in C# with BenchmarkDotNet

BenchmarkDotNet is a library for conducting performance tests on our .NET code.

The performance and speed of our code are important parameters that we must take into account, and sometimes, they are the main point of interest. To measure performance, it is normal to run a test to check its speed, or what we call a benchmark.

However, conducting 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 a tool of some kind (and this is the best one I know of for .NET) the results of your performance tests will not be reliable.

This is where BenchmarkDotNet comes into play, taking care of precisely this. Executing a series of tests so that the results are comparable, repeatable, and accurate.

In addition, its use is very simple, and not too different from conducting a test. Other 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 using the corresponding NuGet package

Install-Package BenchmarkDotNet

Next, we have an example, taken from the library 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 that 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 N parameter that we define can be 1000 or 10000.

Finally, we mark the GlobalSetup function, which will be executed at the beginning 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 a Benchmark is very simple and, more importantly, we can run them knowing that “weird things” won’t happen to deceive us in the results.

Of course, the library has many more options, including generating tables and charts of the results. If you are interested, I advise you to consult the project documentation.

BenchmarkDotNet is Open Source, and the documentation and all the code are available at this link https://github.com/dotnet/BenchmarkDotNet.