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.
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 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.