Skip to content

Commit cad1302

Browse files
committed
Write about benchmarking
1 parent 01d2cf2 commit cad1302

File tree

3 files changed

+147
-1
lines changed

3 files changed

+147
-1
lines changed
+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
---
2+
title: .NET benchmarks
3+
description: Have you ever wanted to test if a solution or algorithm you've written or refactored is performing faster than the previous iteration? In this post, we'll take a look at how you can use the BenchmarkDotNet library to write benchmarks for your C# code.
4+
author: bart
5+
layout: post
6+
image: assets/images/c-sharp/benchmarks.jpeg
7+
caption: This image is generated using Dall-E
8+
prompt: Generate an image of a computer screen with multiple graphs being displayed in a minimalistic flat style
9+
mermaid: false
10+
date: 2024-10-16
11+
categories: [ c-sharp, benchmark, dotnet ]
12+
permalink: csharp/benchmarks
13+
tags: [ .NET, Microsoft, C#, BenchmarkDotNet, Benchmark ]
14+
related: csharp
15+
related_to: [ csharp, dotnet ]
16+
---
17+
18+
Recently I came across the [BenchmarkDotNet](https://benchmarkdotnet.org/) library, which is an open source library
19+
maintained by the .NET foundation.
20+
21+
This library allows us to write benchmarks for our own C# code, just by lookin at the readme on Github, it seems like an
22+
easy to use library. So let's check it out.
23+
24+
## Set up the project
25+
26+
For this post, I'll be using .NET 9 with C# 13 because that's what I've installed on my machine.
27+
This will, however, also work on other .NET versions or different C# versions.
28+
29+
To get started, we create a new project called `DotNetBenchmarks`.
30+
31+
```shell
32+
$ dotnet new console -n DotNetBenchmarks
33+
$ cd DotNetBenchmarks
34+
```
35+
36+
You can also find this project
37+
on [github.com/bartkessels/dotnet-benchmarks](https://github.com/bartkessels/dotnet-benchmarks).
38+
39+
## Add the BenchmarkDotNet package
40+
41+
Next, we can add the [BenchmarkDotNet](https://www.nuget.org/packages/BenchmarkDotNet/) Nuget package to our project.
42+
43+
```shell
44+
$ dotnet add package BenchmarkDotNet
45+
```
46+
47+
And that's all for the installation part.
48+
49+
## Writing our first benchmark
50+
51+
Before we create our first benchmark, let's first think of a use case where we can benchmark multiple implementations.
52+
Let's create a small method that adds multiple numbers.
53+
Yes, I hear you think how is this usefull for two methods? Well, we'll make one method that uses a for loop with a
54+
counter and another method that uses LINQ.
55+
56+
```csharp
57+
internal class Calculator
58+
{
59+
internal int AddUsingLinq(int[] numbers)
60+
{
61+
return numbers.Sum();
62+
}
63+
64+
internal int AddUsingForLoop(int[] numbers)
65+
{
66+
var sum = 0;
67+
68+
for (var i = 0; i < numbers.Length; i++)
69+
{
70+
sum += numbers[i];
71+
}
72+
73+
return sum;
74+
}
75+
}
76+
```
77+
78+
To be completely honest, I don't have any expectations on which method will be faster, or what the actual difference between the two will be.
79+
Let's just try it out.
80+
81+
## Benchmarking the methods
82+
83+
Now that our methods have been written, we simply add the `[Benchmark]` attribute to the methods we want to benchmark.
84+
85+
```csharp
86+
internal class CalculatorBenchmark
87+
{
88+
private readonly Calculator _calculator = new();
89+
90+
[Benchmark]
91+
internal void AddUsingLinq()
92+
{
93+
int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
94+
_calculator.AddUsingLinq(numbers);
95+
}
96+
97+
[Benchmark]
98+
internal void AddUsingForLoop()
99+
{
100+
int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
101+
_calculator.AddUsingForLoop(numbers);
102+
}
103+
}
104+
```
105+
106+
Next we need to register our benchmark class to the benchmark runner in our `Program.cs` file.
107+
108+
```csharp
109+
BenchmarkRunner.Run<CalculatorBenchmark>();
110+
```
111+
112+
Now, let's dive into the code behind the `BenchmarkRunner.Run` method and see how it knows what methods we have declared as benchmarks.
113+
114+
Let's start by opening the `BenchmarkRunner.Run` method in Github, after a little digging I found in the [BenchmarkRunnerDirty](https://github.com/dotnet/BenchmarkDotNet/blob/9040e40187f2bbecea4aec724f995fde378f608b/src/BenchmarkDotNet/Running/BenchmarkRunnerDirty.cs#L21) file.
115+
All this method does, is actually calling the `BenchmarkRunnerClean ` method, which in turn uses the `BenchMarkConverter` to retrieve all methods that have the `Benchmark` attribute, see [BenchmarkConverter.cs; line35](https://github.com/dotnet/BenchmarkDotNet/blob/9040e40187f2bbecea4aec724f995fde378f608b/src/BenchmarkDotNet/Running/BenchmarkConverter.cs#L35).
116+
117+
Then for each method it finds, it will execute it and temporarily save the results, see [BenchmarkRunnerClean.cs; line 121](https://github.com/dotnet/BenchmarkDotNet/blob/9040e40187f2bbecea4aec724f995fde378f608b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs#L121)
118+
119+
### Running the benchmarks
120+
121+
Now we know a bit more on how the benchmarks are run, let's actually run our own benchmarks. It's important to know that it's highly recommended to run the benchmarks in release mode, as debug mode probably impacts the runtime more than you'd like [(Dotnet Foundation and members, n.d.)](https://benchmarkdotnet.org/articles/guides/good-practices.html#use-the-release-build-without-an-attached-debugger).
122+
If for some reason, you run it using debug mode, you get the following message and your benchmarks won't run.
123+
124+
```
125+
// Validating benchmarks:
126+
// * Assembly DotNetBenchmarks which defines benchmarks is non-optimized
127+
Benchmark was built without optimization enabled (most probably a DEBUG configuration). Please, build it in RELEASE.
128+
If you want to debug the benchmarks, please see https://benchmarkdotnet.org/articles/guides/troubleshooting.html#debugging-benchmarks.
129+
```
130+
131+
To run our benchmarks, we just call `dotnet run` and set the configuration flag to `Release`.
132+
133+
```shell
134+
$ dotnet run -c Release
135+
```
136+
137+
After less than two minutes, I got the following result (this may differ for your machine).
138+
139+
```
140+
| Method | Mean | Error | StdDev |
141+
|---------------- |---------:|---------:|---------:|
142+
| AddUsingLinq | 14.50 ns | 0.203 ns | 0.169 ns |
143+
| AddUsingForLoop | 15.01 ns | 0.365 ns | 1.054 ns |
144+
```
145+
146+
As a small extra, we not only know how to write benchmarks for our own classes or methods, we also know that LINQ is faster in summing up numbers than writing your own for-loop.

_posts/c-sharp/c-sharp-12/2024-01-03-ref-readonly.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ date: 2024-01-03
1111
categories: [c-sharp]
1212
permalink: csharp/csharp-12/ref-readonly
1313
tags: [.NET, Microsoft, C#, C# 12]
14-
related: csharp12
14+
r[2023-12-20-introduction.md](2023-12-20-introduction.md)elated: csharp12
1515
related_to: [csharp12, csharp]
1616
---
1717

assets/images/c-sharp/benchmarks.jpeg

143 KB
Loading

0 commit comments

Comments
 (0)