Benchmark and Job Baselines
In order to scale your results, you can mark a benchmark method or a job as a baseline. Let's learn this feature by examples.
Sample: IntroBenchmarkBaseline
You can mark a method as a baseline with the help of [Benchmark(Baseline = true)]
.
Source code
using System.Threading;
using BenchmarkDotNet.Attributes;
namespace BenchmarkDotNet.Samples
{
public class IntroBenchmarkBaseline
{
[Benchmark]
public void Time50() => Thread.Sleep(50);
[Benchmark(Baseline = true)]
public void Time100() => Thread.Sleep(100);
[Benchmark]
public void Time150() => Thread.Sleep(150);
}
}
Output
As a result, you will have additional Ratio
column in the summary table:
| Method | Mean | Error | StdDev | Ratio |
|-------- |----------:|----------:|----------:|------:|
| Time50 | 50.46 ms | 0.0779 ms | 0.0729 ms | 0.50 |
| Time100 | 100.39 ms | 0.0762 ms | 0.0713 ms | 1.00 |
| Time150 | 150.48 ms | 0.0986 ms | 0.0922 ms | 1.50 |
This column contains the mean value of the ratio distribution.
For example, in the case of Time50
, we divide
the first measurement of Time50
into the first measurement of Time100
(it's the baseline),
the second measurement of Time50
into the second measurement of Time100
,
and so on.
Next, we calculate the mean of all these values and display it in the Ratio
column.
For Time50
, we have 0.50.
The Ratio
column was formerly known as Scaled
.
The old title was a source of misunderstanding and confusion because
many developers interpreted it as the ratio of means (e.g., 50.46
/100.39
for Time50
).
The ratio of distribution means and the mean of the ratio distribution are pretty close to each other in most cases,
but they are not equal.
In @BenchmarkDotNet.Samples.IntroRatioStdDev, you can find an example of how this value can be spoiled by outliers.
Links
- Benchmark and Job Baselines
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroBenchmarkBaseline
Sample: IntroRatioSD
The ratio of two benchmarks is not a single number, it's a distribution.
In most simple cases, the range of the ratio distribution is narrow,
and BenchmarkDotNet displays a single column Ratio
with the mean value.
However, it also adds the RatioSD
column (the standard deviation of the ratio distribution)
in complex situations.
In the below example, the baseline benchmark is spoiled by a single outlier
Source code
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using Perfolizer.Mathematics.OutlierDetection;
namespace BenchmarkDotNet.Samples
{
// Don't remove outliers
[Outliers(OutlierMode.DontRemove)]
// Skip jitting, pilot, warmup; measure 10 iterations
[SimpleJob(RunStrategy.Monitoring, iterationCount: 10, invocationCount: 1)]
public class IntroRatioSD
{
private int counter;
[GlobalSetup]
public void Setup() => counter = 0;
[Benchmark(Baseline = true)]
public void Base()
{
Thread.Sleep(100);
if (++counter % 7 == 0)
Thread.Sleep(5000); // Emulate outlier
}
[Benchmark]
public void Slow() => Thread.Sleep(200);
[Benchmark]
public void Fast() => Thread.Sleep(50);
}
}
Output
Here are statistics details for the baseline benchmark:
Mean = 600.6054 ms, StdErr = 500.0012 ms (83.25%); N = 10, StdDev = 1,581.1428 ms
Min = 100.2728 ms, Q1 = 100.3127 ms, Median = 100.4478 ms, Q3 = 100.5011 ms, Max = 5,100.6163 ms
IQR = 0.1884 ms, LowerFence = 100.0301 ms, UpperFence = 100.7837 ms
ConfidenceInterval = [-1,789.8568 ms; 2,991.0677 ms] (CI 99.9%), Margin = 2,390.4622 ms (398.01% of Mean)
Skewness = 2.28, Kurtosis = 6.57, MValue = 2
-------------------- Histogram --------------------
[-541.891 ms ; 743.427 ms) | @@@@@@@@@
[ 743.427 ms ; 2027.754 ms) |
[2027.754 ms ; 3312.082 ms) |
[3312.082 ms ; 4458.453 ms) |
[4458.453 ms ; 5742.780 ms) | @
---------------------------------------------------
As you can see, a single outlier significantly affected the metrics.
Because of this, BenchmarkDotNet adds the Median
and the RatioSD
columns in the summary table:
Method | Mean | Error | StdDev | Median | Ratio | RatioSD |
------- |----------:|--------------:|--------------:|----------:|------:|--------:|
Base | 600.61 ms | 2,390.4622 ms | 1,581.1428 ms | 100.45 ms | 1.00 | 0.00 |
Slow | 200.50 ms | 0.4473 ms | 0.2959 ms | 200.42 ms | 1.80 | 0.62 |
Fast | 50.54 ms | 0.3435 ms | 0.2272 ms | 50.48 ms | 0.45 | 0.16 |
Let's look at the Base
and Slow
benchmarks.
The Mean
values are 600
and 200
milliseconds; the "Scaled Mean" value is 0.3.
The Median
values are 100
and 200
milliseconds; the "Scaled Median" value is 2.
Both values are misleading.
BenchmarkDotNet evaluates the ratio distribution and displays the mean (1.80) and the standard deviation (0.62).
Links
- Benchmark and Job Baselines
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroRatioSD
Sample: IntroCategoryBaseline
The only way to have several baselines in the same class is to separate them by categories
and mark the class with [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
.
Source code
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
namespace BenchmarkDotNet.Samples
{
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class IntroCategoryBaseline
{
[BenchmarkCategory("Fast"), Benchmark(Baseline = true)]
public void Time50() => Thread.Sleep(50);
[BenchmarkCategory("Fast"), Benchmark]
public void Time100() => Thread.Sleep(100);
[BenchmarkCategory("Slow"), Benchmark(Baseline = true)]
public void Time550() => Thread.Sleep(550);
[BenchmarkCategory("Slow"), Benchmark]
public void Time600() => Thread.Sleep(600);
}
}
Output
| Method | Categories | Mean | Error | StdDev | Ratio |
|-------- |----------- |----------:|----------:|----------:|------:|
| Time50 | Fast | 50.46 ms | 0.0745 ms | 0.0697 ms | 1.00 |
| Time100 | Fast | 100.47 ms | 0.0955 ms | 0.0893 ms | 1.99 |
| | | | | | |
| Time550 | Slow | 550.48 ms | 0.0525 ms | 0.0492 ms | 1.00 |
| Time600 | Slow | 600.45 ms | 0.0396 ms | 0.0331 ms | 1.09 |
Links
- Benchmark and Job Baselines
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroCategoryBaseline
Sample: IntroJobBaseline
If you want to compare several runtime configuration,
you can mark one of your jobs with baseline = true
.
Source code
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
namespace BenchmarkDotNet.Samples
{
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net462, baseline: true)]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Mono)]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net50)]
public class IntroJobBaseline
{
[Benchmark]
public int SplitJoin()
=> string.Join(",", new string[1000]).Split(',').Length;
}
}
Output
BenchmarkDotNet=v0.10.12, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Processor=Intel Core i7-6700HQ CPU 2.60GHz (Skylake), ProcessorCount=8
Frequency=2531249 Hz, Resolution=395.0619 ns, Timer=TSC
.NET Core SDK=2.0.3
[Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
Job-MXFYPZ : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2600.0
Core : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
Mono : Mono 5.4.0 (Visual Studio), 64bit
Method | Runtime | Mean | Error | StdDev | Ratio | RatioSD |
---------- |-------- |---------:|----------:|----------:|------:|--------:|
SplitJoin | Clr | 19.42 us | 0.2447 us | 0.1910 us | 1.00 | 0.00 |
SplitJoin | Core | 13.00 us | 0.2183 us | 0.1935 us | 0.67 | 0.01 |
SplitJoin | Mono | 39.14 us | 0.7763 us | 1.3596 us | 2.02 | 0.07 |
Links
- Benchmark and Job Baselines
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroJobBaseline