benchmark C# on WSL

Install both .Net 9.0 and .Net 10.0 on WSL Ubuntu

doc

1
2
3
4
5
6
7
8
9
10
11
12
13
wget https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-9.0.305-linux-x64-binaries
wget https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-10.0.100-rc.1-linux-x64-binaries

mkdir -p ~/dotnet
tar zxf dotnet-sdk-9.0.305-linux-x64.tar.gz -C $HOME/dotnet
tar zxf dotnet-sdk-10.0.100-rc.1.25451.107-linux-x64.tar.gz -C $HOME/dotnet

echo 'export DOTNET_ROOT=$HOME/dotnet' >> ~/.bashrc
echo 'export PATH=$PATH:$DOTNET_ROOT' >> ~/.bashrc
source ~/.bashrc

# verify, reopen the terminal
dotnet --list-sdks

Attributes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

// Entry point, tell application to find the benchmark class - Tests - in the current assembly and run the defined benchmarks.
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);

// Instruct BenchmarkDotNet to generate the assembly language output(disassembly) for the benchmarked method. This is highly useful for low-level performance analysis to see exactly what instructions the JIT compiler produces.
[DisassemblyDiagnoser]

// This attribute enables memory usage tracking, reporting metrics like allocated memory per operation.
// "displayGenColumns: false" simplifies the output by not showing columns for specific garbage collector generations.
[MemoryDiagnoser(displayGenColumns: false)]

// Format output, hid several default columns from the results table to make output cleaner and focus on the most relevant metrics(like Mean, Allocated, etc.).
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD", "y")]

public partial class Tests // Class that contains method to be benchmarked.
{
[Benchmark] // Marks method as the specific code block that BenchmarkDotNet should run and measure repeatedly.
[Arguments(42)] // Benchmark specific argument
public int Sum(int y)
{
Func<int, int> addY = x => x + y;
return DoubleResult(addY, y);
}

private int DoubleResult(Func<int, int> func, int arg)
{
int result = func(arg);
return result + result;
}
}

Phases of benchmark

  • OverheadJitting

  • WorkloadJitting

  • WorkloadPilot

  • OverheadActual (IdleTarget) includes OverheadWarmup and OverheadWorkload

  • WorkloadWarmup (MainWarmup)

  • WorkloadActual (MainTarget)

  • WorkloadResult = WorkloadActual - OverheadActual

  • pilot: The best operation count will be chosen.

  • OverheadWarmup, OverheadWorkload: BenchmarkDotNet overhead will be evaluated.

  • ActualWarmup: Warmup of the workload method.

  • ActualWorkload: Actual measurements.

  • Result = ActualWorkload -