When I code, I typically create a “rough draft” of code and then refactor it so that it can become more performant. One tool that I like to use to test my code’s overall speed is BenchmarkDotNet. BenchmarkDotNet is a .NET library that helps test code by tracking its performance. It is super easy to get started. One of my favorite things about it, is that you write the code you want to test in methods. This is very similar to writing unit tests. If you want to take a look at some of the documentation for BenchmarkDotNet, you can do that here.

In this post, I want to demonstrate a very simple use case by utilizing BenchmarkDotNet. A question I see being asked a lot is whether it is faster to iterate over some sort of collection with a for loop or a foreach statement. So that’s exactly what we will do. We are going to create a few benchmark methods to test the speed of each statement against various sizes of arrays. Let’s get started!

Setting up the Project

For this example, I will just create a simple Console Application with .NET 7 and add a reference to BenchmarkDotNet. To create the project, I will use the dotnet cli tool to create a directory and the Console Application inside of the directory with this:

PS> dotnet new console -o BenchmarkTesting

After the project and the directory have been created, I will add a reference to the NuGet package. If you haven’t read it already, here is my post on adding a reference to a NuGet package.

Adding the Benchmark Class and Benchmark Methods

Setting up benchmarks is easy to do. It is essentially a C# class and class methods with BenchmarkDotNet attributes tied to the class and methods. In our first step, we will add a new C# file to our project. In my example, I just named it MyTests.cs.

Solution-Explorer

In the file, you will add the appropriate using statements. At a minimum, you will have to reference the BenchmarkDotNet.Attributes namespace to get rolling. In our example though, I am going to use these four namespaces:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Reports;

The attributes that I will tie to my class are also optional when getting started.

[Config(typeof(Config))]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class MyTests
{
    // Class Methods
}

The config attribute indicates that I will be setting up a configuration for my benchmarks. The GroupBenchmarksBy attribute indicates that I will be grouping my benchmarks (I will group by the different lengths of arrays). Along with the groupings, I will also use the CategoriesColumn to categorize the groupings for a more readable output.

Within this class I am going to create six benchmarks. I will have six, because we are testing two different methods of iteration against three different sized arrays to gauge the performance. Below I will show the benchmark method for the two small sized arrays. The other four are very similar to these two, but of course you can see all of them in my post’s GitHub repo.

[BenchmarkCategory("10 Iterations"), Benchmark(Baseline = true)]
public void For10()
{
    int[] array = Enumerable.Range(0, 10).ToArray();

    for (int i = 0; i < array.Length; i++)
    {
        Console.WriteLine(array[i]);
    }
}

[BenchmarkCategory("10 Iterations"), Benchmark]
public void ForEach10()
{
    int[] array = Enumerable.Range(0, 10).ToArray();

    foreach (int a in array)
    {
        Console.WriteLine(a);
    }
}

As you can see, the two methods are similar to one another. The only difference being the loops. One is a for loop and the other is a foreach statement. As far as the attributes go, they are fairly simple to understand. The For10() method will be our baseline for this grouping and the ForEach10() will be tested against that baseline. In each of their attributes, you can see that these methods will belong to the “10 Iterations” group and that the For10() method has the true flag set for the baseline. As for the second method, it just has the “Benchmark” attribute because at a minimum, you need to set this for BenchmarkDotNet to know that this method will be used in your benchmark tests.

private class Config : ManualConfig
{
    public Config()
    {
        SummaryStyle = SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
    }
}

At the bottom, I set the configuration to show the ratio style of “trend” for the summarization. This will show “x times faster/slower” trend in the results.

Running the Benchmarks

After you have all of you benchmark methods setup, you will need to call the BenchmarkRunner in your Main() method. In my Program file, I added a reference to the Benchmark.Running namespace and called the BenchmarkRunner so that my benchmarks are ran.

using BenchmarkDotNet.Running;

var summary = BenchmarkRunner.Run<MyTests>();

This is all of the code that is needed to execute the benchmarks. The next thing that is required, is that the program needs to be ran without debugging and to have it’s configuration set to “Release”.

Release-Config

Once the program is run, you will see a lot of output. Depending on what you are trying to benchmark, you could be waiting for the program to end for some time. Because mine was written with the Console.WriteLine() statement, it took some time.

Results

After the program finishes, you get to see the results. For our results, you can see that they are grouped on the categories that we set and each of their baselines.

Benchmark-Results

From these results we can see that the for loop outperformed the foreach statement when iterating over an array with ten elements. However, once the size of the array grew, the foreach statement performed better. Also, we can determine that the foreach statement would be a better choice for even larger arrays as the performance between the array with 100 elements and the array with 1000 elements was even better. With these results, you can also see the mean time that it takes to execute these methods.

BenchmarkDotNet is a reliable, easy-to-use tool for helping you fine tune your code. Again, here is the documentation for BenchmarkDotNet. If you would like, take a look at my full project repo. Have fun benchmarking!