In this note I’ll show how to use BenchmarkDotNet to compare the performance of different versions of the same library.
First of all - these libraries must have the same API (at least that we need to benchmark). Consider the library Lib
with SomethingUsefull
method. For simplicity, this method only freezes the calling thread for some time:
namespace Lib;
public class Class
{
public void SomethingUsefull()
{
Thread.Sleep(1000); // emulates long operation
}
}
We need to configure the project with NuGet properties:
<PropertyGroup>
<Copyright>© 2024, Albert Akhmetov</Copyright>
<Authors>Albert Akhmetov</Authors>
<Product>Lib</Product>
<PackageId>Lib</PackageId>
<Version>0.1.0-alpha1</Version>
</PropertyGroup>
To perform the benchmark, create a console app and add references to BenchmarkDotNet and the baseline version of our library:
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Lib" Version="0.1.0-alpha1"/>
</ItemGroup>
For testing purposes we can use the local NuGet repository. For example, let’s use .target
directory for NuGet packages. To use this directory in our console test project add the following NuGet.config
:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local" value="../.target/" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
Add the following Bench
class to the test project:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
[Config(typeof(Config))]
public class Bench
{
private class Config : ManualConfig
{
public Config()
{
var job = Job.Default
.WithStrategy(RunStrategy.Monitoring) // only monitoring
.WithWarmupCount(3) // light warm-up
.WithIterationCount(10) // only 10 interations
.WithLaunchCount(1); // launch a test only once
// add the jobs for all versions:
AddJob(
job.WithNuGet("Lib", "0.1.0-alpha1").WithId("alpha1").WithBaseline(true),
job.WithNuGet("Lib", "0.1.0-alpha2").WithId("alpha2"),
job.WithNuGet("Lib", "0.1.0-alpha3").WithId("alpha3"),
job.WithNuGet("Lib", "0.1.0-beta").WithId("beta")
);
// add the percentage column
SummaryStyle =
SummaryStyle.Default
.WithRatioStyle(BenchmarkDotNet.Columns.RatioStyle.Percentage);
}
}
[Benchmark]
public void SomethingUsefull()
{
new Lib.Class().SomethingUsefull();
}
}
To run a benchmark add the following code to Program.cs
:
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<Bench>();
We need to put all the required NuGet packages into .target
folder. The easy way to do it is to use the following command:
dotnet pack -c:Release -o "../.target"
Our versions differ only for timeout for Sleep
method:
Version | Timeout |
---|---|
0.1.0-alpha1 | 1000 |
0.1.0-alpha2 | 800 |
0.1.0-alpha3 | 200 |
0.1.0-beta | 1500 |
Change timeout and a version number, then execute dotnet pack
command. As a result .target
directory will contain the following packages:
Lib.0.1.0-alpha1.nupkg
Lib.0.1.0-alpha2.nupkg
Lib.0.1.0-alpha3.nupkg
Lib.0.1.0-beta.nupkg
At this time we’ve got all we need to run our benchmark. To run the benchmark use the following command for the test app:
dotnet run -c:Release
After the benchmark is passed you’ll see the following table:
Method | Job | NuGetReferences | Mean | Error | StdDev | Ratio | RatioSD |
---|---|---|---|---|---|---|---|
SomethingUsefull | alpha1 | Lib 0.1.0-alpha1 | 1,007.7 ms | 6.30 ms | 4.17 ms | baseline | |
SomethingUsefull | alpha2 | Lib 0.1.0-alpha2 | 807.2 ms | 7.83 ms | 5.18 ms | -20% | 0.7% |
SomethingUsefull | alpha3 | Lib 0.1.0-alpha3 | 110.5 ms | 7.83 ms | 5.18 ms | -89% | 4.5% |
SomethingUsefull | beta | Lib 0.1.0-beta | 1,508.5 ms | 9.43 ms | 6.24 ms | +50% | 0.6% |
Sample code is available here.