Benchmark your code in .NET using BenchmarkDotNet - A Guide for .NET Developer
Intro
BenchmarkDotNet is a powerful, open-source library designed to make benchmarking and performance analysis in .NET straightforward and reliable. Whether youβre optimizing a high-traffic web application, refining a library, or simply trying to ensure your code is running at its best, BenchmarkDotNet provides the tools you need to get precise, actionable insights.
In this post, weβll explore what BenchmarkDotNet is, how it simplifies the benchmarking process, and how you can leverage it to enhance the performance of your .NET applications. Weβll dive into the core features, setup instructions, and some practical examples to help you get started on your performance optimization journey.
Official BenchmarkDotNet site
Did we really need it?
For me is YES. Benchmarking is crucial for several reasons when it comes to developing efficient and high-performing applications. Which we can Identifying Bottlenecks and fine tuning our application to ensure it can run as fast as possible and bonus with less resources required like impacting memory allocation
,IO
and etc.
Simple Setup Project For Benchmark Code
First, lets define our BenchmarkDotNetExample.csproj
:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
</ItemGroup>
</Project>
This .NET project file (typically named BenchmarkDotNetExample.csproj.csproj
) is configured as follows:
- Project Type: It's set to build an executable application
(<OutputType>Exe</OutputType>)
. - Target Framework: It targets .NET 8.0
(<TargetFramework>net8.0</TargetFramework>)
. - Implicit Usings: Implicit using directives are enabled
(<ImplicitUsings>enable</ImplicitUsings>)
, which means common namespaces will be automatically included in code files. - Nullable Reference Types: Nullable reference types are enabled
(<Nullable>enable</Nul*lable>)
, allowing the use of nullable annotations to improve code safety and clarity. - Heroes are here!! π Package Reference: It includes a reference to the
BenchmarkDotNet
package version0.13.12
during my time of writing this blog post, which is a library used for benchmarking code performance.
Next, i create a new class Enums.cs
which have three value.
Enum: UserType
with three values: Guest
, Staff
, Admin
.
And I also creating a new static method called GetFastEnum
with parameter from the UserType
. Pretty simple kan πΆοΈ.
namespace BenchmarkDotNetExample
{
public sealed class Enums
{
public enum UserType
{
Guest,
Staff,
Admin
}
public static string GetFastEnum(UserType userType)
{
switch (userType)
{
case UserType.Guest:
return nameof(userType.Guest);
case UserType.Staff:
return nameof(userType.Staff);
case UserType.Admin: return nameof(userType.Admin);
default:
return userType.ToString();
}
}
}
}
Next, I created another class called BenchyEnum.cs
which will handle the benchmark process.
using BenchmarkDotNet.Attributes;
namespace BenchmarkDotNetExample
{
[MemoryDiagnoser]
public class BenchyEnum
{
[Benchmark(Baseline = true)]
public string NaiveEnumString()
{
return Enums.UserType.Admin.ToString();
}
[Benchmark]
public string FastEnum()
{
return Enums.GetFastEnum(Enums.UserType.Admin);
}
}
}
Info
[MemoryDiagnoser]: Adds memory usage diagnostics to the benchmarks.
[Benchmark(Baseline = true)]: This method serves as the baseline for performance comparison. Which we set this naive way (π) as our baseline code to compare with the other implementation. Pretty nice right!.
[Benchmark] : Any method that required us to benchmark the performance, should mark as this attribute.
Next, we instruct our code to execute the benchmark.
Note: Always run in
release
mode.
using BenchmarkDotNet.Running;
using BenchmarkDotNetExample;
BenchmarkRunner.Run<BenchyEnum>();
Result
BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
AMD Ryzen 5 4600H with Radeon Graphics, 1 CPU, 12 logical and 6 physical cores
.NET SDK 8.0.303
[Host] : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|---------------- |-----------:|----------:|----------:|------:|-------:|----------:|------------:|
| NaiveEnumString | 11.0700 ns | 0.2749 ns | 0.3763 ns | 1.000 | 0.0115 | 24 B | 1.00 |
| FastEnum | 0.0164 ns | 0.0073 ns | 0.0061 ns | 0.001 | - | - | 0.00 |
Done π. So, what we can say about the result. As far as i`m concern (still learning btw π€£).
- NaiveEnumString (baseline) takes an average of
11.0700 nanoseconds
and allocates24 bytes
of memory. - FastEnum performs significantly faster, with an average time of
0.0164 nanoseconds
, and it allocates no memory (indicated by0 bytes
).
Performance Ratio:
- FastEnum is approximately
671,000 times
faster thanNaiveEnumString
and does not allocate memory, making it more efficient in terms of both speed and memory usage.
Updated (12/09/2024) βοΈ
You can use the custom style config for better output summary. For instance, as per below sample i adde custom StyleConfig
which inherits from ManualConfig
. The format I choose is Percentage
. Which showing us how many improve from the rest items. In this case i can say 99% improve compare to baseline
code.
You can three types available that meet your scope of output which is Value
,Percentage
and Trend
.
private class StyleConfig : ManualConfig
{
public StyleConfig()
=> SummaryStyle = SummaryStyle.Default.WithRatioStyle(RatioStyle.Percentage);
}
[MemoryDiagnoser]
[Config(typeof(StyleConfig))] <--new added code π
public class BenchyEnum
{
... \\ code hide for brevity π
Results after using custom style.
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---------------- |-----------:|----------:|----------:|-----------:|---------:|--------:|-------:|----------:|------------:|
| NaiveEnumString | 13.7891 ns | 0.3407 ns | 0.4056 ns | 13.6955 ns | baseline | | 0.0115 | 24 B | |
| FastEnum | 0.0529 ns | 0.0518 ns | 0.0985 ns | 0.0000 ns | -99.3% | 126.2% | - | - | -100% |
Conclusion
BenchmarkDotNet is a crucial tool for .NET developers aiming to optimize performance. It delivers precise performance metrics and helps ensure efficient resource utilization. By incorporating BenchmarkDotNet into their development workflow, developers can make data-driven decisions that lead to improved performance outcomes for their applications.
Thanks for reading guys.