Statistics
Sample: IntroStatisticsColumns
Source code
using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
namespace BenchmarkDotNet.Samples
{
[MediumRunJob, SkewnessColumn, KurtosisColumn]
public class IntroStatisticsColumns
{
private const int N = 10000;
private readonly byte[] data;
private readonly MD5 md5 = MD5.Create();
private readonly SHA256 sha256 = SHA256.Create();
public IntroStatisticsColumns()
{
data = new byte[N];
new Random(42).NextBytes(data);
}
[Benchmark(Baseline = true)]
public byte[] Md5A() => md5.ComputeHash(data);
[Benchmark]
public byte[] Md5B() => md5.ComputeHash(data);
[Benchmark]
public byte[] Sha256() => sha256.ComputeHash(data);
}
}
Output
Method | Mean | Error | StdDev | Skewness | Kurtosis | Ratio | RatioSD |
---|---|---|---|---|---|---|---|
Md5A | 15.91 us | 0.0807 us | 0.1209 us | 0.4067 | 1.646 | 1.00 | 0.00 |
Md5B | 15.89 us | 0.0709 us | 0.1062 us | 0.5893 | 2.141 | 1.00 | 0.01 |
Sha256 | 36.62 us | 0.6390 us | 0.9564 us | 1.1363 | 4.014 | 2.30 | 0.06 |
Links
- Statistics
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroStatisticsColumns
Sample: IntroPercentiles
The percentile represents a higher boundary for specified percentage of the measurements. For example, 95th percentile = 500ms means that 95% of all samples are not slower than 500ms. This metric is not very useful in microbenchmarks, as the values from consequent runs have a very narrow distribution. However, real-world scenarios often have so-called long tail distribution (due to IO delays, locks, memory access latency and so on), so the average execution time cannot be trusted.
The percentiles allow to include the tail of distribution into the comparison.
However, it requires some preparations steps.
At first, you should have enough runs to count percentiles from.
The IterationCount
in the config should be set to 10-20 runs at least.
Second, the count of iterations for each run should not be very high, or the peak timings will be averaged.
The IterationTime = 25
works fine for most cases;
for long-running benchmarks the Mode = Mode.SingleRun
will be the best choice.
However, feel free to experiment with the config values.
Third, if you want to be sure that measurements are repeatable, set the LaunchCount
to 3 or higher.
And last, don't forget to include the columns into the config.
They are not included by default (as said above, these are not too useful for most of the benchmarks).
There're predefined StatisticColumn.P0
..StatisticColumn.P100
for absolute timing percentiles.
Example
Run the IntroPercentiles sample. It contains three benchmark methods.
- First delays for 20 ms constantly.
- The second has random delays for 10..30 ms.
- And the third delays for 10ms 85 times of 100 and delays for 40ms 15 times of 100.
Here's the output from the benchmark (some columns removed for brevity):
Method | Median | StdDev | Ratio | P0 | P50 | P80 | P85 | P95 | P100 |
---|---|---|---|---|---|---|---|---|---|
ConstantDelays | 20.3813 ms | 0.2051 ms | 1.00 | 20.0272 ms | 20.3813 ms | 20.4895 ms | 20.4954 ms | 20.5869 ms | 21.1471 ms |
RandomDelays | 19.8055 ms | 5.7556 ms | 0.97 | 10.0793 ms | 19.8055 ms | 25.4173 ms | 26.5187 ms | 29.0313 ms | 29.4550 ms |
RareDelays | 10.3385 ms | 11.4828 ms | 0.51 | 10.0157 ms | 10.3385 ms | 10.5211 ms | 40.0560 ms | 40.3992 ms | 40.4674 ms |
Also, it's very easy to screw the results with incorrect setup. For example, the same code being run with
new Job
{
IterationCount = 5,
IterationTime = 500
}
completely hides the peak values:
Method | Median | StdDev | Ratio | P0 | P50 | P80 | P85 | P95 | P100 |
---|---|---|---|---|---|---|---|---|---|
ConstantDelays | 20.2692 ms | 0.0308 ms | 1.00 | 20.1986 ms | 20.2692 ms | 20.2843 ms | 20.2968 ms | 20.3097 ms | 20.3122 ms |
RandomDelays | 18.9965 ms | 0.8601 ms | 0.94 | 18.1339 ms | 18.9965 ms | 19.8126 ms | 19.8278 ms | 20.4485 ms | 20.9466 ms |
RareDelays | 14.0912 ms | 2.8619 ms | 0.70 | 10.2606 ms | 14.0912 ms | 15.7653 ms | 17.3862 ms | 18.6728 ms | 18.6940 ms |
Source code
using System;
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Engines;
namespace BenchmarkDotNet.Samples
{
// Using percentiles for adequate timings representation
[Config(typeof(Config))]
[SimpleJob(RunStrategy.ColdStart, launchCount: 4,
warmupCount: 3, iterationCount: 20, id: "MyJob")]
public class IntroPercentiles
{
// To share between runs.
// DO NOT do this in production code. The System.Random IS NOT thread safe.
private static readonly Random Rnd = new Random();
private class Config : ManualConfig
{
public Config()
{
AddColumn(
StatisticColumn.P0,
StatisticColumn.P25,
StatisticColumn.P50,
StatisticColumn.P67,
StatisticColumn.P80,
StatisticColumn.P85,
StatisticColumn.P90,
StatisticColumn.P95,
StatisticColumn.P100);
}
}
[Benchmark(Baseline = true)]
public void ConstantDelays() => Thread.Sleep(20);
[Benchmark]
public void RandomDelays() => Thread.Sleep(10 + (int) (20 * Rnd.NextDouble()));
[Benchmark]
public void RareDelays()
{
int rndTime = 10;
// Bigger delays for 15% of the runs
if (Rnd.NextDouble() > 0.85)
{
rndTime += 30;
}
Thread.Sleep(rndTime);
}
}
}
Links
- Statistics
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroPercentiles
Sample: IntroRankColumn
Source code
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Mathematics;
using BenchmarkDotNet.Order;
namespace BenchmarkDotNet.Samples
{
[ShortRunJob]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
[RankColumn(NumeralSystem.Arabic)]
[RankColumn(NumeralSystem.Roman)]
[RankColumn(NumeralSystem.Stars)]
public class IntroRankColumn
{
[Params(1, 2)]
public int Factor;
[Benchmark]
public void Foo() => Thread.Sleep(Factor * 100);
[Benchmark]
public void Bar() => Thread.Sleep(Factor * 200);
}
}
Output
Method | Factor | Mean | Error | StdDev | Rank | Rank | Rank |
------- |------- |---------:|---------:|----------:|-----:|-----:|-----:|
Foo | 1 | 100.8 ms | 2.250 ms | 0.1272 ms | 1 | I | * |
Foo | 2 | 200.8 ms | 4.674 ms | 0.2641 ms | 2 | II | ** |
Bar | 1 | 200.9 ms | 2.012 ms | 0.1137 ms | 2 | II | ** |
Bar | 2 | 400.7 ms | 4.509 ms | 0.2548 ms | 3 | III | *** |
Links
- Statistics
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroRankColumn
Sample: IntroMultimodal
Source code
using System;
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
namespace BenchmarkDotNet.Samples
{
[MValueColumn]
[SimpleJob(RunStrategy.Throughput, 1, 0, -1, 1, "MyJob")]
public class IntroMultimodal
{
private readonly Random rnd = new Random(42);
private void Multimodal(int n)
=> Thread.Sleep((rnd.Next(n) + 1) * 100);
[Benchmark] public void Unimodal() => Multimodal(1);
[Benchmark] public void Bimodal() => Multimodal(2);
[Benchmark] public void Trimodal() => Multimodal(3);
[Benchmark] public void Quadrimodal() => Multimodal(4);
}
}
Output
Method | Mean | Error | StdDev | Median | MValue |
------------ |---------:|-----------:|------------:|---------:|-------:|
Unimodal | 100.5 ms | 0.0713 ms | 0.0667 ms | 100.5 ms | 2.000 |
Bimodal | 144.5 ms | 16.9165 ms | 49.8787 ms | 100.6 ms | 3.571 |
Trimodal | 182.5 ms | 27.4285 ms | 80.8734 ms | 200.5 ms | 4.651 |
Quadrimodal | 226.6 ms | 37.2269 ms | 109.7644 ms | 200.7 ms | 5.882 |
// * Warnings *
MultimodalDistribution
IntroMultimodal.Bimodal: MainJob -> It seems that the distribution is bimodal (mValue = 3.57)
IntroMultimodal.Trimodal: MainJob -> It seems that the distribution is multimodal (mValue = 4.65)
IntroMultimodal.Quadrimodal: MainJob -> It seems that the distribution is multimodal (mValue = 5.88)
Links
- Statistics
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroMultimodal
Sample: IntroOutliers
Source code
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using Perfolizer.Mathematics.OutlierDetection;
namespace BenchmarkDotNet.Samples
{
[Config(typeof(Config))]
public class IntroOutliers
{
private class Config : ManualConfig
{
public Config()
{
var jobBase = Job.Default.WithWarmupCount(0).WithIterationCount(10).WithInvocationCount(1).WithUnrollFactor(1);
AddJob(jobBase.WithOutlierMode(OutlierMode.DontRemove).WithId("DontRemoveOutliers"));
AddJob(jobBase.WithOutlierMode(OutlierMode.RemoveUpper).WithId("RemoveUpperOutliers"));
}
}
private int counter;
[Benchmark]
public void Foo()
{
counter++;
int noise = counter % 10 == 0 ? 500 : 0;
Thread.Sleep(100 + noise);
}
}
}
Output
Method | Job | OutlierMode | Mean | Error | StdDev |
------- |-------------------- |------------ |---------:|------------:|------------:|
Foo | DontRemoveOutliers | DontRemove | 150.5 ms | 239.1911 ms | 158.2101 ms |
Foo | RemoveUpperOutliers | RemoveUpper | 100.5 ms | 0.1931 ms | 0.1149 ms |
// * Hints *
Outliers
IntroOutliers.Foo: DontRemoveOutliers -> 1 outlier was detected
IntroOutliers.Foo: RemoveUpperOutliers -> 1 outlier was removed
Links
- Statistics
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroOutliers