Skip to content

Commit e75bdd8

Browse files
Engine.Run() should return the full list of performed measurements (fixes #2187) (#2188)
* Engine.Run() should return the full list of performed measurements (fixes #2187) * Code review fixes
1 parent 9e759f9 commit e75bdd8

File tree

12 files changed

+186
-80
lines changed

12 files changed

+186
-80
lines changed

src/BenchmarkDotNet/Engines/Engine.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class Engine : IEngine
4141
private bool EvaluateOverhead { get; }
4242
private bool MemoryRandomization { get; }
4343

44+
private readonly List<Measurement> jittingMeasurements = new (10);
4445
private readonly EnginePilotStage pilotStage;
4546
private readonly EngineWarmupStage warmupStage;
4647
private readonly EngineActualStage actualStage;
@@ -104,8 +105,10 @@ public void Dispose()
104105

105106
public RunResults Run()
106107
{
108+
var measurements = new List<Measurement>();
109+
measurements.AddRange(jittingMeasurements);
110+
107111
long invokeCount = TargetJob.ResolveValue(RunMode.InvocationCountCharacteristic, Resolver, 1);
108-
IReadOnlyList<Measurement> idle = null;
109112

110113
if (EngineEventSource.Log.IsEnabled())
111114
EngineEventSource.Log.BenchmarkStart(BenchmarkName);
@@ -114,21 +117,23 @@ public RunResults Run()
114117
{
115118
if (Strategy != RunStrategy.Monitoring)
116119
{
117-
invokeCount = pilotStage.Run();
120+
var pilotStageResult = pilotStage.Run();
121+
invokeCount = pilotStageResult.PerfectInvocationCount;
122+
measurements.AddRange(pilotStageResult.Measurements);
118123

119124
if (EvaluateOverhead)
120125
{
121-
warmupStage.RunOverhead(invokeCount, UnrollFactor);
122-
idle = actualStage.RunOverhead(invokeCount, UnrollFactor);
126+
measurements.AddRange(warmupStage.RunOverhead(invokeCount, UnrollFactor));
127+
measurements.AddRange(actualStage.RunOverhead(invokeCount, UnrollFactor));
123128
}
124129
}
125130

126-
warmupStage.RunWorkload(invokeCount, UnrollFactor, Strategy);
131+
measurements.AddRange(warmupStage.RunWorkload(invokeCount, UnrollFactor, Strategy));
127132
}
128133

129134
Host.BeforeMainRun();
130135

131-
var main = actualStage.RunWorkload(invokeCount, UnrollFactor, forceSpecific: Strategy == RunStrategy.Monitoring);
136+
measurements.AddRange(actualStage.RunWorkload(invokeCount, UnrollFactor, forceSpecific: Strategy == RunStrategy.Monitoring));
132137

133138
Host.AfterMainRun();
134139

@@ -141,7 +146,7 @@ public RunResults Run()
141146

142147
var outlierMode = TargetJob.ResolveValue(AccuracyMode.OutlierModeCharacteristic, Resolver);
143148

144-
return new RunResults(idle, main, outlierMode, workGcHasDone, threadingStats, exceptionFrequency);
149+
return new RunResults(measurements, outlierMode, workGcHasDone, threadingStats, exceptionFrequency);
145150
}
146151

147152
public Measurement RunIteration(IterationData data)
@@ -183,6 +188,8 @@ public Measurement RunIteration(IterationData data)
183188
// Results
184189
var measurement = new Measurement(0, data.IterationMode, data.IterationStage, data.Index, totalOperations, clockSpan.GetNanoseconds());
185190
WriteLine(measurement.ToString());
191+
if (measurement.IterationStage == IterationStage.Jitting)
192+
jittingMeasurements.Add(measurement);
186193

187194
Consume(stackMemory);
188195

src/BenchmarkDotNet/Engines/EnginePilotStage.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
11
using System;
2+
using System.Collections.Generic;
23
using BenchmarkDotNet.Environments;
34
using BenchmarkDotNet.Jobs;
5+
using BenchmarkDotNet.Reports;
6+
using JetBrains.Annotations;
47
using Perfolizer.Horology;
58

69
namespace BenchmarkDotNet.Engines
710
{
811
// TODO: use clockResolution
912
internal class EnginePilotStage : EngineStage
1013
{
14+
public readonly struct PilotStageResult
15+
{
16+
public long PerfectInvocationCount { get; }
17+
[NotNull]
18+
public IReadOnlyList<Measurement> Measurements { get; }
19+
20+
public PilotStageResult(long perfectInvocationCount, [NotNull] List<Measurement> measurements)
21+
{
22+
PerfectInvocationCount = perfectInvocationCount;
23+
Measurements = measurements;
24+
}
25+
26+
public PilotStageResult(long perfectInvocationCount)
27+
{
28+
PerfectInvocationCount = perfectInvocationCount;
29+
Measurements = Array.Empty<Measurement>();
30+
}
31+
}
32+
1133
internal const long MaxInvokeCount = (long.MaxValue / 2 + 1) / 2;
1234

1335
private readonly int unrollFactor;
@@ -30,11 +52,11 @@ public EnginePilotStage(IEngine engine) : base(engine)
3052
}
3153

3254
/// <returns>Perfect invocation count</returns>
33-
public long Run()
55+
public PilotStageResult Run()
3456
{
3557
// If InvocationCount is specified, pilot stage should be skipped
3658
if (TargetJob.HasValue(RunMode.InvocationCountCharacteristic))
37-
return TargetJob.Run.InvocationCount;
59+
return new PilotStageResult(TargetJob.Run.InvocationCount);
3860

3961
// Here we want to guess "perfect" amount of invocation
4062
return TargetJob.HasValue(RunMode.IterationTimeCharacteristic)
@@ -45,15 +67,17 @@ public long Run()
4567
/// <summary>
4668
/// A case where we don't have specific iteration time.
4769
/// </summary>
48-
private long RunAuto()
70+
private PilotStageResult RunAuto()
4971
{
5072
long invokeCount = Autocorrect(minInvokeCount);
73+
var measurements = new List<Measurement>();
5174

5275
int iterationCounter = 0;
5376
while (true)
5477
{
5578
iterationCounter++;
5679
var measurement = RunIteration(IterationMode.Workload, IterationStage.Pilot, iterationCounter, invokeCount, unrollFactor);
80+
measurements.Add(measurement);
5781
double iterationTime = measurement.Nanoseconds;
5882
double operationError = 2.0 * resolution / invokeCount; // An operation error which has arisen due to the Chronometer precision
5983

@@ -75,15 +99,16 @@ private long RunAuto()
7599
}
76100
WriteLine();
77101

78-
return invokeCount;
102+
return new PilotStageResult(invokeCount, measurements);
79103
}
80104

81105
/// <summary>
82106
/// A case where we have specific iteration time.
83107
/// </summary>
84-
private long RunSpecific()
108+
private PilotStageResult RunSpecific()
85109
{
86110
long invokeCount = Autocorrect(Engine.MinInvokeCount);
111+
var measurements = new List<Measurement>();
87112

88113
int iterationCounter = 0;
89114

@@ -92,6 +117,7 @@ private long RunSpecific()
92117
{
93118
iterationCounter++;
94119
var measurement = RunIteration(IterationMode.Workload, IterationStage.Pilot, iterationCounter, invokeCount, unrollFactor);
120+
measurements.Add(measurement);
95121
double actualIterationTime = measurement.Nanoseconds;
96122
long newInvokeCount = Autocorrect(Math.Max(minInvokeCount, (long)Math.Round(invokeCount * targetIterationTime / actualIterationTime)));
97123

@@ -105,7 +131,7 @@ private long RunSpecific()
105131
}
106132
WriteLine();
107133

108-
return invokeCount;
134+
return new PilotStageResult(invokeCount, measurements);
109135
}
110136

111137
private long Autocorrect(long count) => (count + unrollFactor - 1) / unrollFactor * unrollFactor;

src/BenchmarkDotNet/Engines/EngineWarmupStage.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ internal class EngineWarmupStage : EngineStage
99

1010
public EngineWarmupStage(IEngine engine) : base(engine) => this.engine = engine;
1111

12-
public void RunOverhead(long invokeCount, int unrollFactor)
12+
public IReadOnlyList<Measurement> RunOverhead(long invokeCount, int unrollFactor)
1313
=> Run(invokeCount, IterationMode.Overhead, unrollFactor, RunStrategy.Throughput);
1414

15-
public void RunWorkload(long invokeCount, int unrollFactor, RunStrategy runStrategy)
15+
public IReadOnlyList<Measurement> RunWorkload(long invokeCount, int unrollFactor, RunStrategy runStrategy)
1616
=> Run(invokeCount, IterationMode.Workload, unrollFactor, runStrategy);
1717

18-
internal List<Measurement> Run(long invokeCount, IterationMode iterationMode, int unrollFactor, RunStrategy runStrategy)
18+
internal IReadOnlyList<Measurement> Run(long invokeCount, IterationMode iterationMode, int unrollFactor, RunStrategy runStrategy)
1919
{
2020
var criteria = DefaultStoppingCriteriaFactory.Instance.CreateWarmup(engine.TargetJob, engine.Resolver, iterationMode, runStrategy);
2121
return Run(criteria, invokeCount, iterationMode, IterationStage.Warmup, unrollFactor);

src/BenchmarkDotNet/Engines/RunResults.cs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using BenchmarkDotNet.Extensions;
56
using BenchmarkDotNet.Mathematics;
67
using BenchmarkDotNet.Reports;
78
using JetBrains.Annotations;
@@ -13,39 +14,51 @@ public struct RunResults
1314
{
1415
private readonly OutlierMode outlierMode;
1516

17+
[NotNull, PublicAPI]
18+
public IReadOnlyList<Measurement> EngineMeasurements { get; }
19+
1620
[CanBeNull, PublicAPI]
17-
public IReadOnlyList<Measurement> Overhead { get; }
21+
public IReadOnlyList<Measurement> Overhead
22+
=> EngineMeasurements
23+
.Where(m => m.Is(IterationMode.Overhead, IterationStage.Actual))
24+
.ToArray();
1825

1926
[NotNull, PublicAPI]
20-
public IReadOnlyList<Measurement> Workload { get; }
27+
public IReadOnlyList<Measurement> Workload
28+
=> EngineMeasurements
29+
.Where(m => m.Is(IterationMode.Workload, IterationStage.Actual))
30+
.ToArray();
2131

2232
public GcStats GCStats { get; }
2333

2434
public ThreadingStats ThreadingStats { get; }
2535

2636
public double ExceptionFrequency { get; }
2737

28-
public RunResults([CanBeNull] IReadOnlyList<Measurement> overhead,
29-
[NotNull] IReadOnlyList<Measurement> workload,
30-
OutlierMode outlierMode,
31-
GcStats gcStats,
32-
ThreadingStats threadingStats,
33-
double exceptionFrequency)
38+
public RunResults([NotNull] IReadOnlyList<Measurement> engineMeasurements,
39+
OutlierMode outlierMode,
40+
GcStats gcStats,
41+
ThreadingStats threadingStats,
42+
double exceptionFrequency)
3443
{
3544
this.outlierMode = outlierMode;
36-
Overhead = overhead;
37-
Workload = workload;
45+
EngineMeasurements = engineMeasurements;
3846
GCStats = gcStats;
3947
ThreadingStats = threadingStats;
4048
ExceptionFrequency = exceptionFrequency;
4149
}
4250

43-
public IEnumerable<Measurement> GetMeasurements()
51+
public IEnumerable<Measurement> GetWorkloadResultMeasurements()
4452
{
45-
double overhead = Overhead == null ? 0.0 : new Statistics(Overhead.Select(m => m.Nanoseconds)).Median;
46-
var mainStats = new Statistics(Workload.Select(m => m.Nanoseconds));
53+
var overheadActualMeasurements = Overhead ?? Array.Empty<Measurement>();
54+
var workloadActualMeasurements = Workload;
55+
if (workloadActualMeasurements.IsEmpty())
56+
yield break;
57+
58+
double overhead = overheadActualMeasurements.IsEmpty() ? 0.0 : new Statistics(overheadActualMeasurements.Select(m => m.Nanoseconds)).Median;
59+
var mainStats = new Statistics(workloadActualMeasurements.Select(m => m.Nanoseconds));
4760
int resultIndex = 0;
48-
foreach (var measurement in Workload)
61+
foreach (var measurement in workloadActualMeasurements)
4962
{
5063
if (mainStats.IsActualOutlier(measurement.Nanoseconds, outlierMode))
5164
continue;
@@ -63,9 +76,17 @@ public IEnumerable<Measurement> GetMeasurements()
6376
}
6477
}
6578

79+
public IEnumerable<Measurement> GetAllMeasurements()
80+
{
81+
foreach (var measurement in EngineMeasurements)
82+
yield return measurement;
83+
foreach (var measurement in GetWorkloadResultMeasurements())
84+
yield return measurement;
85+
}
86+
6687
public void Print(TextWriter outWriter)
6788
{
68-
foreach (var measurement in GetMeasurements())
89+
foreach (var measurement in GetWorkloadResultMeasurements())
6990
outWriter.WriteLine(measurement.ToString());
7091

7192
if (!GCStats.Equals(GcStats.Empty))

src/BenchmarkDotNet/Toolchains/Results/ExecuteResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ internal ExecuteResult(List<Measurement> measurements, GcStats gcStats, Threadin
6767
internal static ExecuteResult FromRunResults(RunResults runResults, int exitCode)
6868
=> exitCode != 0
6969
? CreateFailed(exitCode)
70-
: new ExecuteResult(runResults.GetMeasurements().ToList(), runResults.GCStats, runResults.ThreadingStats, runResults.ExceptionFrequency);
70+
: new ExecuteResult(runResults.GetAllMeasurements().ToList(), runResults.GCStats, runResults.ThreadingStats, runResults.ExceptionFrequency);
7171

7272
internal static ExecuteResult CreateFailed(int exitCode = -1)
7373
=> new ExecuteResult(false, exitCode, default, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), 0);

tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,11 @@ public RunResults Run()
7272
Console.WriteLine(EngineRunMessage);
7373

7474
return new RunResults(
75-
new List<Measurement> { new Measurement(1, IterationMode.Overhead, IterationStage.Actual, 1, 1, 1) },
76-
new List<Measurement> { new Measurement(1, IterationMode.Workload, IterationStage.Actual, 1, 1, 1) },
75+
new List<Measurement>
76+
{
77+
new (1, IterationMode.Overhead, IterationStage.Actual, 1, 1, 1),
78+
new (1, IterationMode.Workload, IterationStage.Actual, 1, 1, 1)
79+
},
7780
OutlierMode.DontRemove,
7881
default,
7982
default,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System.Linq;
2+
using System.Threading;
3+
using BenchmarkDotNet.Attributes;
4+
using BenchmarkDotNet.Configs;
5+
using BenchmarkDotNet.Engines;
6+
using BenchmarkDotNet.Jobs;
7+
using BenchmarkDotNet.Reports;
8+
using Xunit;
9+
using Xunit.Abstractions;
10+
11+
namespace BenchmarkDotNet.IntegrationTests
12+
{
13+
public class EngineTests : BenchmarkTestExecutor
14+
{
15+
public EngineTests(ITestOutputHelper output) : base(output) { }
16+
17+
[Fact]
18+
public void ZeroWarmupCountIsApplied()
19+
{
20+
var job = Job.InProcess
21+
.WithEvaluateOverhead(false)
22+
.WithWarmupCount(0)
23+
.WithIterationCount(1)
24+
.WithInvocationCount(1)
25+
.WithUnrollFactor(1);
26+
var config = DefaultConfig.Instance.AddJob(job).WithOptions(ConfigOptions.DisableOptimizationsValidator);
27+
var summary = CanExecute<FooBench>(config);
28+
var report = summary.Reports.Single();
29+
int workloadWarmupCount = report.AllMeasurements
30+
.Count(m => m.Is(IterationMode.Workload, IterationStage.Warmup));
31+
Assert.Equal(0, workloadWarmupCount);
32+
}
33+
34+
[Fact]
35+
public void AllMeasurementsArePerformedDefault() => AllMeasurementsArePerformed(Job.Default);
36+
37+
[Fact]
38+
public void AllMeasurementsArePerformedInProcess() => AllMeasurementsArePerformed(Job.InProcess);
39+
40+
private void AllMeasurementsArePerformed(Job baseJob)
41+
{
42+
var job = baseJob
43+
.WithWarmupCount(1)
44+
.WithIterationCount(1)
45+
.WithInvocationCount(1)
46+
.WithUnrollFactor(1);
47+
var config = DefaultConfig.Instance.AddJob(job).WithOptions(ConfigOptions.DisableOptimizationsValidator);
48+
var summary = CanExecute<FooBench>(config);
49+
var measurements = summary.Reports.Single().AllMeasurements;
50+
51+
Output.WriteLine("*** AllMeasurements ***");
52+
foreach (var measurement in measurements)
53+
Output.WriteLine(measurement.ToString());
54+
Output.WriteLine("-----");
55+
56+
void Check(IterationMode mode, IterationStage stage)
57+
{
58+
int count = measurements.Count(m => m.Is(mode, stage));
59+
Output.WriteLine($"Count({mode}{stage}) = {count}");
60+
Assert.True(count > 0, $"AllMeasurements don't contain {mode}{stage}");
61+
}
62+
63+
Check(IterationMode.Overhead, IterationStage.Jitting);
64+
Check(IterationMode.Workload, IterationStage.Jitting);
65+
Check(IterationMode.Overhead, IterationStage.Warmup);
66+
Check(IterationMode.Overhead, IterationStage.Actual);
67+
Check(IterationMode.Workload, IterationStage.Warmup);
68+
Check(IterationMode.Workload, IterationStage.Actual);
69+
Check(IterationMode.Workload, IterationStage.Result);
70+
}
71+
72+
public class FooBench
73+
{
74+
[Benchmark]
75+
public void Foo() => Thread.Sleep(10);
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)