
Loading ..
Please wait while we prepare...
3/20/2025
When you analyze your code's performance correctly, it helps you make good decisions about how to write your program, whether it's running fast, or if there are better ways to write the code. If you have two versions of the same code, you can determine which one is faster and which one consumes fewer resources.
This type of analysis helps you understand how consistent and accurate the test results are.
📌 There might be some natural variations in performance due to factors like the operating environment or the hardware used.
Mean: This is the average time taken by the different operations.
Mean = (Sum of all test values) / (Number of tests)
Standard Deviation: A measure that shows how far the results are from the mean.
Min (Minimum): The shortest time measured in the tests.
Max (Maximum): The longest time measured in the tests.
Median: This is the value that comes in the middle when you sort the test results from the lowest to the highest. It can be more accurate than the mean when there are many outliers (values that are very far from the others).
Analyzing Performance Stability: Statistical analysis gives us an idea of how stable the results are across a set of tests. It can reveal if there is a variance in performance due to different things like system load or memory usage.
Detecting Outliers: Strange values might appear due to specific situations like hardware problems or code errors. Statistical analysis helps us find these values and separate them from the normal results.
Making Informed Decisions: When you understand the test results statistically, you can make better decisions about improving the code or making the necessary changes to improve performance. For example, if there is a large variance in the results, you can look for the cause and see if you need to improve the algorithm or the system.
Comparing Different Solutions: When you compare more than one way to solve the same problem, statistical analysis gives you the information you need to know which method is more efficient. This helps you make decisions based on numbers, not just guesses.
This type of analysis relies on comparing different methods. Comparisons give you a clear view of which method performs better in terms of execution speed, memory usage, or any other important criteria. For example: you can compare between two algorithms to solve the same problem.
This is the time it takes for the system to finish executing an operation or a set of operations.
Performance Measurement Criteria:
ns (Nanosecond): A nanosecond is a very small fraction of a second. One nanosecond is equal to 1 billionth of a second (i.e., 10^-9 of a second).
ms (Millisecond): A millisecond is 1 thousandth of a second (i.e., 10^-3 of a second).
Source code for the example on GitHub
Create a new project:
dotnet new console -n StringConcatenationBenchmarkDotNet
cd StringConcatenationBenchmarkDotNet
Add the BenchmarkDotNet package:
dotnet add package BenchmarkDotNet
Let's try concatenating a first name with a last name in different ways:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text;
namespace StringConcatenationBenchmarkDotNet
{
public class StringConcatBenchmarks
{
// This is the test data
private readonly string firstName = "Saif";
private readonly string lastName = "Saidi";
// Using the "+" operator to concatenate strings
[Benchmark]
public string UsingPlusOperator_Benchmark()
{
return "Hello, " + firstName + " " + lastName + "!";
}
// Using String Interpolation to concatenate strings
[Benchmark]
public string UsingStringInterpolation_Benchmark()
{
return $"Hello, {firstName} {lastName}!";
}
// Using String.Concat to concatenate strings
[Benchmark]
public string UsingStringConcat_Benchmark()
{
return string.Concat("Hello, ", firstName, " ", lastName, "!");
}
// Using StringBuilder to concatenate strings
[Benchmark]
public string UsingStringBuilder_Benchmark()
{
var sb = new StringBuilder();
sb.Append("Hello, ");
sb.Append(firstName);
sb.Append(" ");
sb.Append(lastName);
sb.Append("!");
return sb.ToString();
}
// Using String.Format to concatenate strings
[Benchmark]
public string UsingStringFormat_Benchmark()
{
return string.Format("Hello, {0} {1}!", firstName, lastName);
}
}
}
📌 Note: Always run performance tests in "Release" mode to get accurate results.
public static void Main(stringargs)
{
BenchmarkRunner.Run<StringConcatBenchmarks>();
}
dotnet run -c Release
# Or activate the Release option in Visual Studio and run the project
BenchmarkDotNet v0.13.12, OS Windows 10.0.19045.3930 (22H2/November2022Update)
Intel Core i7-8665U CPU 1.90GHz (Coffee Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK 9.0.104
| Method | Mean | Error | StdDev | Median |
|--------------------------------------|----------:|----------:|----------:|----------:|
| UsingPlusOperator_Benchmark | 42.79 ns | 1.140 ns | 3.324 ns | 41.98 ns |
| UsingStringInterpolation_Benchmark | 39.73 ns | 0.996 ns | 2.937 ns | 39.19 ns |
| UsingStringConcat_Benchmark | 32.25 ns | 0.206 ns | 0.182 ns | 32.19 ns |
| UsingStringBuilder_Benchmark | 65.86 ns | 2.461 ns | 7.022 ns | 63.93 ns |
| UsingStringFormat_Benchmark | 66.32 ns | 1.417 ns | 3.807 ns | 64.61 ns |
Key Metrics Explained:
UsingStringConcat_Benchmark
, as it took the least time (32.25 nanoseconds) and had the lowest standard deviation (0.182 nanoseconds), meaning it is also the most stable.UsingStringBuilder_Benchmark
and UsingStringFormat_Benchmark
, as they took about 65.86 and 66.32 nanoseconds respectively, and they are among the methods that took the most time compared to the others.[Benchmark]
: Used to specify the functions whose performance will be tested.[MemoryDiagnoser]
: When this attribute is added, BenchmarkDotNet starts tracking how memory is allocated during the tests and displays a report on memory usage.[Params]
: Used to specify different values for the inputs, to test the same function multiple times with different inputs.public class MyBenchmark
{
[Params(10, 100, 1000)] // Different values to try
public int Size;
[Benchmark]
public void MyTestMethod()
{
var list = new List<int>(Size);
// Execution of the test with the list size
}
}
[Setup]
, [GlobalSetup]
, [Cleanup]
:[Setup]
: Executed before each benchmark, meaning if there are many benchmarks in the class, this will be executed many times.[GlobalSetup]
: Executed once before all benchmarks in the class. We mainly use it when we need to do a general setup only once.[Cleanup]
: Used to clean up data or release resources after all benchmarks are finished. It can be used to perform a shutdown after the benchmarks are executed.public class MyBenchmark
{
[Setup]
public void Setup()
{
// Preparing the data or environment for the benchmarks
}
[Benchmark]
public void MyTestMethod()
{
// The benchmark is here
}
}
ShortRunJob is a fast option in BenchmarkDotNet to reduce the time spent on performance tests. This allows for quick results for small or fast-executing code, but the accuracy of the statistics is lower.
Job.Default
This is the default setting in BenchmarkDotNet, where tests are run with accuracy, a large number of iterations, and the time needed to obtain statistically accurate results.
ShortRunJob
It works by reducing the duration of test execution and the number of iterations, and it is preferred when quick results are needed from small code tests or for rapid experiments.
[ShortRunJob] // Add this property
public class ShortJobsBenchmarks
{
}
In BenchmarkDotNet, we use ManualConfig
to make custom settings and fully control how the tests are executed. Usually, when you use BenchmarkDotNet, the tests are executed using the default settings, but ManualConfig
allows you to customize a range of options such as job settings, adding extra attributes, customizing report settings, and many other options.
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
// ... other code
// Setting up ManualConfig
var config = new ManualConfig()
.AddLogger(ConsoleLogger.Default); // Adding a logger to see the progress in the Console
// Running the benchmarks using the custom configuration
BenchmarkRunner.Run<StringConcatBenchmarks>(config);
AddLogger(ConsoleLogger.Default);
You can use any logger to see what's happening.
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
// ... other code
var config = new ManualConfig()
.AddLogger(ConsoleLogger.Default);
// Running the benchmarks using the custom configuration
BenchmarkRunner.Run<StringConcatBenchmarks>(config);
Job.ShortRun
: ShortRunJob is used to reduce the execution time of the benchmarks.Job.LongRun
: LongRun is used to increase the execution time of the benchmarks.AddJob(Job.Default.WithWarmupCount(5).WithIterationCount(10))
: You can specify the number of warmup counts and the number of iteration counts for the benchmark.using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Jobs;
// ... other code
var config = new ManualConfig()
.AddLogger(ConsoleLogger.Default)
.AddJob(Job.ShortRun)
.AddJob(Job.Default.WithWarmupCount(5).WithIterationCount(10));
// Running the benchmarks using the custom configuration
BenchmarkRunner.Run<StringConcatBenchmarks>(config);
You can customize the columns displayed in the benchmark results table using the ManualConfig. For example, to show the Minimum and Maximum execution times, you can use the following code:
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Columns; // Make sure this namespace is included
using BenchmarkDotNet.Jobs;
// ... other code
var config = new ManualConfig()
.AddLogger(ConsoleLogger.Default)
.AddJob(Job.ShortRun)
.AddJob(Job.Default.WithWarmupCount(5).WithIterationCount(10))
.AddColumn(StatisticColumn.Min) // To show the Minimum
.AddColumn(StatisticColumn.Max); // To show the Maximum
.AddColumnProvider(DefaultColumnProviders.Instance);// Defaults Columns
// Running the benchmarks using the custom configuration
BenchmarkRunner.Run<StringConcatBenchmarks>(config);