Hi I`m Faris!

I'm a Senior Software Engineer working with .NET Framework,.NET Core, Entity Framework, Design Pattern, SQL Server, SQLite, Web Development, And I`m Still Learning πŸ•ΆοΈ.

Me : Assist To Simplified .NET Development, Provide .NET Best Practise,Sharing Knowledged.

Benchmark your code in .NET using BenchmarkDotNet - A Guide for .NET Developer

06 August 2024 by Admin
dotnetBenchmarkDotNet
...

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 version 0.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 allocates 24 bytes of memory.
  • FastEnum performs significantly faster, with an average time of 0.0164 nanoseconds, and it allocates no memory (indicated by 0 bytes).

Performance Ratio:

  • FastEnum is approximately 671,000 times faster than NaiveEnumString 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.

Source Code ❀️