Skip to content

Commit deedc64

Browse files
committed
Issue #1024: Calculate baseline by the fastest benchmark
1 parent c02c3d8 commit deedc64

File tree

10 files changed

+119
-3
lines changed

10 files changed

+119
-3
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using BenchmarkDotNet.Configs;
2+
using System;
3+
4+
namespace BenchmarkDotNet.Attributes
5+
{
6+
[AttributeUsage(AttributeTargets.Class)]
7+
public class AutomaticBaselineAttribute : Attribute, IConfigSource
8+
{
9+
public IConfig Config { get; }
10+
11+
public AutomaticBaselineAttribute(AutomaticBaselineMode mode) => Config = ManualConfig.CreateEmpty().WithAutomaticBaseline(mode);
12+
}
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace BenchmarkDotNet.Configs
2+
{
3+
public enum AutomaticBaselineMode
4+
{
5+
None,
6+
Fastest
7+
}
8+
}

src/BenchmarkDotNet/Configs/DebugConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,7 @@ public string ArtifactsPath
8787
public ConfigOptions Options => ConfigOptions.KeepBenchmarkFiles | ConfigOptions.DisableOptimizationsValidator;
8888

8989
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => emptyConclusion;
90+
91+
public AutomaticBaselineMode AutomaticBaselineMode { get; } = AutomaticBaselineMode.None;
9092
}
9193
}

src/BenchmarkDotNet/Configs/DefaultConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,7 @@ public string ArtifactsPath
106106
public IEnumerable<IFilter> GetFilters() => Array.Empty<IFilter>();
107107

108108
public IEnumerable<IColumnHidingRule> GetColumnHidingRules() => Array.Empty<IColumnHidingRule>();
109+
110+
public AutomaticBaselineMode AutomaticBaselineMode { get; } = AutomaticBaselineMode.None;
109111
}
110112
}

src/BenchmarkDotNet/Configs/IConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,7 @@ public interface IConfig
5757
/// Collect any errors or warnings when composing the configuration
5858
/// </summary>
5959
IReadOnlyList<Conclusion> ConfigAnalysisConclusion { get; }
60+
61+
AutomaticBaselineMode AutomaticBaselineMode { get; }
6062
}
6163
}

src/BenchmarkDotNet/Configs/ImmutableConfig.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ internal ImmutableConfig(
5353
SummaryStyle summaryStyle,
5454
ConfigOptions options,
5555
TimeSpan buildTimeout,
56-
IReadOnlyList<Conclusion> configAnalysisConclusion)
56+
IReadOnlyList<Conclusion> configAnalysisConclusion,
57+
AutomaticBaselineMode automaticBaselineMode)
5758
{
5859
columnProviders = uniqueColumnProviders;
5960
loggers = uniqueLoggers;
@@ -74,6 +75,7 @@ internal ImmutableConfig(
7475
Options = options;
7576
BuildTimeout = buildTimeout;
7677
ConfigAnalysisConclusion = configAnalysisConclusion;
78+
AutomaticBaselineMode = automaticBaselineMode;
7779
}
7880

7981
public ConfigUnionRule UnionRule { get; }
@@ -83,6 +85,7 @@ internal ImmutableConfig(
8385
[NotNull] public IOrderer Orderer { get; }
8486
public SummaryStyle SummaryStyle { get; }
8587
public TimeSpan BuildTimeout { get; }
88+
public AutomaticBaselineMode AutomaticBaselineMode { get; }
8689

8790
public IEnumerable<IColumnProvider> GetColumnProviders() => columnProviders;
8891
public IEnumerable<IExporter> GetExporters() => exporters;

src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ public static ImmutableConfig Create(IConfig source)
7171
source.SummaryStyle ?? SummaryStyle.Default,
7272
source.Options,
7373
source.BuildTimeout,
74-
configAnalyse.AsReadOnly()
74+
configAnalyse.AsReadOnly(),
75+
source.AutomaticBaselineMode
7576
);
7677
}
7778

src/BenchmarkDotNet/Configs/ManualConfig.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class ManualConfig : IConfig
5353
[PublicAPI] public IOrderer Orderer { get; set; }
5454
[PublicAPI] public SummaryStyle SummaryStyle { get; set; }
5555
[PublicAPI] public TimeSpan BuildTimeout { get; set; } = DefaultConfig.Instance.BuildTimeout;
56+
[PublicAPI] public AutomaticBaselineMode AutomaticBaselineMode { get; private set; }
5657

5758
public IReadOnlyList<Conclusion> ConfigAnalysisConclusion => emptyConclusion;
5859

@@ -98,6 +99,12 @@ public ManualConfig WithBuildTimeout(TimeSpan buildTimeout)
9899
return this;
99100
}
100101

102+
public ManualConfig WithAutomaticBaseline(AutomaticBaselineMode automaticBaselineMode)
103+
{
104+
AutomaticBaselineMode = automaticBaselineMode;
105+
return this;
106+
}
107+
101108
[EditorBrowsable(EditorBrowsableState.Never)]
102109
[Obsolete("This method will soon be removed, please start using .AddColumn() instead.")]
103110
public void Add(params IColumn[] newColumns) => AddColumn(newColumns);
@@ -254,6 +261,7 @@ public void Add(IConfig config)
254261
columnHidingRules.AddRange(config.GetColumnHidingRules());
255262
Options |= config.Options;
256263
BuildTimeout = GetBuildTimeout(BuildTimeout, config.BuildTimeout);
264+
AutomaticBaselineMode = config.AutomaticBaselineMode;
257265
}
258266

259267
/// <summary>

src/BenchmarkDotNet/Reports/Summary.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class Summary
3737
private ImmutableDictionary<BenchmarkCase, BenchmarkReport> ReportMap { get; }
3838
private BaseliningStrategy BaseliningStrategy { get; }
3939
private bool? isMultipleRuntimes;
40+
private readonly BenchmarkCase inferredBaselineBenchmarkCase;
4041

4142
public Summary(
4243
string title,
@@ -62,13 +63,32 @@ public Summary(
6263
DisplayPrecisionManager = new DisplayPrecisionManager(this);
6364
Orderer = GetConfiguredOrdererOrDefaultOne(reports.Select(report => report.BenchmarkCase.Config));
6465
BenchmarksCases = Orderer.GetSummaryOrder(reports.Select(report => report.BenchmarkCase).ToImmutableArray(), this).ToImmutableArray(); // we sort it first
66+
inferredBaselineBenchmarkCase = GetFastestBenchmarkCase(reports);
6567
Reports = BenchmarksCases.Select(b => ReportMap[b]).ToImmutableArray(); // we use sorted collection to re-create reports list
6668
BaseliningStrategy = BaseliningStrategy.Create(BenchmarksCases);
6769
Style = GetConfiguredSummaryStyleOrDefaultOne(BenchmarksCases).WithCultureInfo(cultureInfo);
6870
Table = GetTable(Style);
6971
AllRuntimes = BuildAllRuntimes(HostEnvironmentInfo, Reports);
7072
}
7173

74+
private static BenchmarkCase GetFastestBenchmarkCase(ImmutableArray<BenchmarkReport> reports)
75+
{
76+
if (reports.Any() && reports.All(r => r.BenchmarkCase.Config.AutomaticBaselineMode == AutomaticBaselineMode.Fastest))
77+
{
78+
var fastestReport = reports.First();
79+
foreach (var report in reports.Skip(1))
80+
{
81+
if (report.ResultStatistics.Mean < fastestReport.ResultStatistics.Mean)
82+
{
83+
fastestReport = report;
84+
}
85+
}
86+
return fastestReport.BenchmarkCase;
87+
}
88+
89+
return null;
90+
}
91+
7292
[PublicAPI] public bool HasReport(BenchmarkCase benchmarkCase) => ReportMap.ContainsKey(benchmarkCase);
7393

7494
/// <summary>
@@ -133,7 +153,11 @@ public string GetLogicalGroupKey(BenchmarkCase benchmarkCase)
133153
=> Orderer.GetLogicalGroupKey(BenchmarksCases, benchmarkCase);
134154

135155
public bool IsBaseline(BenchmarkCase benchmarkCase)
136-
=> BaseliningStrategy.IsBaseline(benchmarkCase);
156+
{
157+
return inferredBaselineBenchmarkCase != null
158+
? inferredBaselineBenchmarkCase == benchmarkCase
159+
: BaseliningStrategy.IsBaseline(benchmarkCase);
160+
}
137161

138162
[CanBeNull]
139163
public BenchmarkCase GetBaseline(string logicalGroupKey)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Columns;
3+
using BenchmarkDotNet.Configs;
4+
using BenchmarkDotNet.Jobs;
5+
using BenchmarkDotNet.Reports;
6+
using BenchmarkDotNet.Running;
7+
using System.Linq;
8+
using System.Threading;
9+
using Xunit;
10+
11+
namespace BenchmarkDotNet.IntegrationTests
12+
{
13+
public class AutomaticBaselineTests
14+
{
15+
[Fact]
16+
public void AutomaticBaselineSelectionIsCorrect()
17+
{
18+
var config = CreateConfig();
19+
20+
var summary = BenchmarkRunner.Run<BaselineSample>();
21+
22+
var table = summary.GetTable(SummaryStyle.Default);
23+
var column = table.Columns.Single(c => c.Header == "Ratio");
24+
Assert.Equal(2, column.Content.Length);
25+
Assert.Equal(1.0, double.Parse(column.Content[1])); // Ratio of TwoMilliseconds
26+
Assert.True(double.Parse(column.Content[0]) > 1.0); // Ratio of TwoHundredMilliseconds
27+
}
28+
29+
[AutomaticBaseline(AutomaticBaselineMode.Fastest)]
30+
public class BaselineSample
31+
{
32+
[Benchmark]
33+
public void TwoHundredMilliseconds()
34+
{
35+
Thread.Sleep(200);
36+
}
37+
38+
[Benchmark]
39+
public void TwoMilliseconds()
40+
{
41+
Thread.Sleep(2);
42+
}
43+
}
44+
45+
private IConfig CreateConfig()
46+
=> ManualConfig.CreateEmpty()
47+
.AddJob(Job.ShortRun
48+
.WithEvaluateOverhead(false) // no need to run idle for this test
49+
.WithWarmupCount(0) // don't run warmup to save some time for our CI runs
50+
.WithIterationCount(1)) // single iteration is enough for us
51+
.AddColumnProvider(DefaultColumnProviders.Instance);
52+
}
53+
}

0 commit comments

Comments
 (0)