Skip to content

Commit fee6992

Browse files
committed
Refactored engine JIT stage.
1 parent 81a4e55 commit fee6992

23 files changed

+912
-646
lines changed

src/BenchmarkDotNet/Engines/Engine.cs

Lines changed: 91 additions & 115 deletions
Large diffs are not rendered by default.
Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using BenchmarkDotNet.Characteristics;
43
using BenchmarkDotNet.Jobs;
54
using BenchmarkDotNet.Mathematics;
65
using BenchmarkDotNet.Reports;
@@ -9,22 +8,31 @@
98

109
namespace BenchmarkDotNet.Engines
1110
{
12-
internal abstract class EngineActualStage(IterationMode iterationMode) : EngineStage(IterationStage.Actual, iterationMode)
11+
internal abstract class EngineActualStage(IterationMode iterationMode, long invokeCount, int unrollFactor, EngineParameters parameters) : EngineStage(IterationStage.Actual, iterationMode, parameters)
1312
{
1413
internal const int MaxOverheadIterationCount = 20;
1514

16-
internal static EngineActualStage GetOverhead(IEngine engine)
17-
=> new EngineActualStageAuto(engine.TargetJob, engine.Resolver, IterationMode.Overhead);
15+
internal readonly long invokeCount = invokeCount;
16+
internal readonly int unrollFactor = unrollFactor;
1817

19-
internal static EngineActualStage GetWorkload(IEngine engine, RunStrategy strategy)
18+
internal static EngineActualStage GetOverhead(long invokeCount, int unrollFactor, EngineParameters parameters)
19+
=> new EngineActualStageAuto(IterationMode.Overhead, invokeCount, unrollFactor, parameters);
20+
21+
internal static EngineActualStage GetWorkload(RunStrategy strategy, long invokeCount, int unrollFactor, EngineParameters parameters)
2022
{
21-
var targetJob = engine.TargetJob;
23+
var targetJob = parameters.TargetJob;
2224
int? iterationCount = targetJob.ResolveValueAsNullable(RunMode.IterationCountCharacteristic);
2325
const int DefaultWorkloadCount = 10;
2426
return iterationCount == null && strategy != RunStrategy.Monitoring
25-
? new EngineActualStageAuto(targetJob, engine.Resolver, IterationMode.Workload)
26-
: new EngineActualStageSpecific(iterationCount ?? DefaultWorkloadCount, IterationMode.Workload);
27+
? new EngineActualStageAuto(IterationMode.Workload, invokeCount, unrollFactor, parameters)
28+
: new EngineActualStageSpecific(iterationCount ?? DefaultWorkloadCount, IterationMode.Workload, invokeCount, unrollFactor, parameters);
2729
}
30+
31+
protected IterationData GetIterationData()
32+
=> new(Mode, Stage, ++iterationIndex, invokeCount, unrollFactor, parameters.IterationSetupAction, parameters.IterationCleanupAction,
33+
Mode == IterationMode.Workload
34+
? unrollFactor == 1 ? parameters.WorkloadActionNoUnroll : parameters.WorkloadActionUnroll
35+
: unrollFactor == 1 ? parameters.OverheadActionNoUnroll : parameters.OverheadActionUnroll);
2836
}
2937

3038
internal sealed class EngineActualStageAuto : EngineActualStage
@@ -37,22 +45,23 @@ internal sealed class EngineActualStageAuto : EngineActualStage
3745
private readonly List<Measurement> measurementsForStatistics;
3846
private int iterationCounter = 0;
3947

40-
public EngineActualStageAuto(Job targetJob, IResolver resolver, IterationMode iterationMode) : base(iterationMode)
48+
public EngineActualStageAuto(IterationMode iterationMode, long invokeCount, int unrollFactor, EngineParameters parameters) : base(iterationMode, invokeCount, unrollFactor, parameters)
4149
{
42-
maxRelativeError = targetJob.ResolveValue(AccuracyMode.MaxRelativeErrorCharacteristic, resolver);
43-
maxAbsoluteError = targetJob.ResolveValueAsNullable(AccuracyMode.MaxAbsoluteErrorCharacteristic);
44-
outlierMode = targetJob.ResolveValue(AccuracyMode.OutlierModeCharacteristic, resolver);
45-
minIterationCount = targetJob.ResolveValue(RunMode.MinIterationCountCharacteristic, resolver);
46-
maxIterationCount = targetJob.ResolveValue(RunMode.MaxIterationCountCharacteristic, resolver);
50+
maxRelativeError = parameters.TargetJob.ResolveValue(AccuracyMode.MaxRelativeErrorCharacteristic, parameters.Resolver);
51+
maxAbsoluteError = parameters.TargetJob.ResolveValueAsNullable(AccuracyMode.MaxAbsoluteErrorCharacteristic);
52+
outlierMode = parameters.TargetJob.ResolveValue(AccuracyMode.OutlierModeCharacteristic, parameters.Resolver);
53+
minIterationCount = parameters.TargetJob.ResolveValue(RunMode.MinIterationCountCharacteristic, parameters.Resolver);
54+
maxIterationCount = parameters.TargetJob.ResolveValue(RunMode.MaxIterationCountCharacteristic, parameters.Resolver);
4755
measurementsForStatistics = GetMeasurementList();
4856
}
4957

5058
internal override List<Measurement> GetMeasurementList() => new(maxIterationCount);
5159

52-
internal override bool GetShouldRunIteration(List<Measurement> measurements, ref long invokeCount)
60+
internal override bool GetShouldRunIteration(List<Measurement> measurements, out IterationData iterationData)
5361
{
5462
if (measurements.Count == 0)
5563
{
64+
iterationData = GetIterationData();
5665
return true;
5766
}
5867

@@ -72,25 +81,36 @@ internal override bool GetShouldRunIteration(List<Measurement> measurements, ref
7281

7382
if (iterationCounter >= minIterationCount && actualError < maxError)
7483
{
84+
iterationData = default;
7585
return false;
7686
}
7787

7888
if (iterationCounter >= maxIterationCount || isOverhead && iterationCounter >= MaxOverheadIterationCount)
7989
{
90+
iterationData = default;
8091
return false;
8192
}
8293

94+
iterationData = GetIterationData();
8395
return true;
8496
}
8597
}
8698

87-
internal sealed class EngineActualStageSpecific(int maxIterationCount, IterationMode iterationMode) : EngineActualStage(iterationMode)
99+
internal sealed class EngineActualStageSpecific(int maxIterationCount, IterationMode iterationMode, long invokeCount, int unrollFactor, EngineParameters parameters)
100+
: EngineActualStage(iterationMode, invokeCount, unrollFactor, parameters)
88101
{
89-
private int iterationCount = 0;
90-
91102
internal override List<Measurement> GetMeasurementList() => new(maxIterationCount);
92103

93-
internal override bool GetShouldRunIteration(List<Measurement> measurements, ref long invokeCount)
94-
=> ++iterationCount <= maxIterationCount;
104+
internal override bool GetShouldRunIteration(List<Measurement> measurements, out IterationData iterationData)
105+
{
106+
if (iterationIndex < maxIterationCount)
107+
{
108+
iterationData = GetIterationData();
109+
return true;
110+
}
111+
112+
iterationData = default;
113+
return false;
114+
}
95115
}
96116
}
Lines changed: 4 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,15 @@
1-
using System;
2-
using BenchmarkDotNet.Jobs;
3-
using Perfolizer.Horology;
4-
51
namespace BenchmarkDotNet.Engines
62
{
73
public class EngineFactory : IEngineFactory
84
{
95
public IEngine CreateReadyToRun(EngineParameters engineParameters)
106
{
11-
if (engineParameters.WorkloadActionNoUnroll == null)
12-
throw new ArgumentNullException(nameof(engineParameters.WorkloadActionNoUnroll));
13-
if (engineParameters.WorkloadActionUnroll == null)
14-
throw new ArgumentNullException(nameof(engineParameters.WorkloadActionUnroll));
15-
if (engineParameters.Dummy1Action == null)
16-
throw new ArgumentNullException(nameof(engineParameters.Dummy1Action));
17-
if (engineParameters.Dummy2Action == null)
18-
throw new ArgumentNullException(nameof(engineParameters.Dummy2Action));
19-
if (engineParameters.Dummy3Action == null)
20-
throw new ArgumentNullException(nameof(engineParameters.Dummy3Action));
21-
if (engineParameters.OverheadActionNoUnroll == null)
22-
throw new ArgumentNullException(nameof(engineParameters.OverheadActionNoUnroll));
23-
if (engineParameters.OverheadActionUnroll == null)
24-
throw new ArgumentNullException(nameof(engineParameters.OverheadActionUnroll));
25-
if (engineParameters.TargetJob == null)
26-
throw new ArgumentNullException(nameof(engineParameters.TargetJob));
27-
28-
engineParameters.GlobalSetupAction?.Invoke(); // whatever the settings are, we MUST call global setup here, the global cleanup is part of Engine's Dispose
29-
30-
if (!engineParameters.NeedsJitting) // just create the engine, do NOT jit
31-
return CreateMultiActionEngine(engineParameters);
32-
33-
int jitIndex = 0;
34-
35-
if (engineParameters.HasInvocationCount || engineParameters.HasUnrollFactor) // it's a job with explicit configuration, just create the engine and jit it
36-
{
37-
var warmedUpMultiActionEngine = CreateMultiActionEngine(engineParameters);
38-
39-
DeadCodeEliminationHelper.KeepAliveWithoutBoxing(Jit(warmedUpMultiActionEngine, ++jitIndex, invokeCount: engineParameters.UnrollFactor, unrollFactor: engineParameters.UnrollFactor));
40-
41-
return warmedUpMultiActionEngine;
42-
}
43-
44-
var singleActionEngine = CreateSingleActionEngine(engineParameters);
45-
var singleInvocationTime = Jit(singleActionEngine, ++jitIndex, invokeCount: 1, unrollFactor: 1);
46-
double timesPerIteration = engineParameters.IterationTime / singleInvocationTime; // how many times can we run given benchmark per iteration
47-
48-
if ((timesPerIteration < 1.5) && (singleInvocationTime < TimeInterval.FromSeconds(10.0)))
49-
{
50-
// if the Jitting took more than IterationTime/1.5 but still less than 10s (a magic number based on observations of reported bugs)
51-
// we call it one more time to see if Jitting itself has not dominated the first invocation
52-
// if it did, it should NOT be a single invocation engine (see #837, #1337, #1338, and #1780)
53-
singleInvocationTime = Jit(singleActionEngine, ++jitIndex, invokeCount: 1, unrollFactor: 1);
54-
timesPerIteration = engineParameters.IterationTime / singleInvocationTime;
55-
}
56-
57-
// executing once takes longer than iteration time => long running benchmark, needs no pilot and no overhead
58-
// Or executing twice would put us well past the iteration time ==> needs no pilot and no overhead
59-
if (timesPerIteration < 1.5)
60-
return singleActionEngine;
61-
62-
int defaultUnrollFactor = Job.Default.ResolveValue(RunMode.UnrollFactorCharacteristic, EngineParameters.DefaultResolver);
63-
int roundedUpTimesPerIteration = (int)Math.Ceiling(timesPerIteration);
64-
65-
if (roundedUpTimesPerIteration < defaultUnrollFactor) // if we run it defaultUnrollFactor times per iteration, it's going to take longer than IterationTime
66-
{
67-
var needsPilot = engineParameters.TargetJob
68-
.WithUnrollFactor(1) // we don't want to use unroll factor!
69-
.WithMinInvokeCount(2) // the minimum is 2 (not the default 4 which can be too much and not 1 which we already know is not enough)
70-
.WithEvaluateOverhead(false); // it's something very time consuming, it overhead is too small compared to total time
71-
72-
return CreateEngine(engineParameters, needsPilot, engineParameters.OverheadActionNoUnroll, engineParameters.WorkloadActionNoUnroll);
73-
}
74-
75-
var multiActionEngine = CreateMultiActionEngine(engineParameters);
76-
77-
DeadCodeEliminationHelper.KeepAliveWithoutBoxing(Jit(multiActionEngine, ++jitIndex, invokeCount: defaultUnrollFactor, unrollFactor: defaultUnrollFactor));
78-
79-
return multiActionEngine;
80-
}
81-
82-
/// <returns>the time it took to run the benchmark</returns>
83-
private static TimeInterval Jit(Engine engine, int jitIndex, int invokeCount, int unrollFactor)
84-
{
85-
engine.Dummy1Action.Invoke();
86-
87-
DeadCodeEliminationHelper.KeepAliveWithoutBoxing(engine.RunIteration(new IterationData(IterationMode.Overhead, IterationStage.Jitting, jitIndex, invokeCount, unrollFactor))); // don't forget to JIT idle
7+
var engine = new Engine(engineParameters);
888

89-
engine.Dummy2Action.Invoke();
9+
// TODO: Move GlobalSetup/Cleanup to Engine.Run.
10+
engine.Parameters.GlobalSetupAction.Invoke(); // whatever the settings are, we MUST call global setup here, the global cleanup is part of Engine's Dispose
9011

91-
var result = engine.RunIteration(new IterationData(IterationMode.Workload, IterationStage.Jitting, jitIndex, invokeCount, unrollFactor));
92-
93-
engine.Dummy3Action.Invoke();
94-
95-
engine.WriteLine();
96-
97-
return TimeInterval.FromNanoseconds(result.Nanoseconds);
12+
return engine;
9813
}
99-
100-
private static Engine CreateMultiActionEngine(EngineParameters engineParameters)
101-
=> CreateEngine(engineParameters, engineParameters.TargetJob, engineParameters.OverheadActionUnroll, engineParameters.WorkloadActionUnroll);
102-
103-
private static Engine CreateSingleActionEngine(EngineParameters engineParameters)
104-
=> CreateEngine(engineParameters,
105-
engineParameters.TargetJob
106-
.WithInvocationCount(1).WithUnrollFactor(1) // run the benchmark exactly once per iteration
107-
.WithEvaluateOverhead(false), // it's something very time consuming, it overhead is too small compared to total time
108-
// todo: consider if we should set the warmup count to 2
109-
engineParameters.OverheadActionNoUnroll,
110-
engineParameters.WorkloadActionNoUnroll);
111-
112-
private static Engine CreateEngine(EngineParameters engineParameters, Job job, Action<long> idle, Action<long> main)
113-
=> new Engine(
114-
engineParameters.Host,
115-
EngineParameters.DefaultResolver,
116-
engineParameters.Dummy1Action,
117-
engineParameters.Dummy2Action,
118-
engineParameters.Dummy3Action,
119-
idle,
120-
main,
121-
job,
122-
engineParameters.GlobalSetupAction,
123-
engineParameters.GlobalCleanupAction,
124-
engineParameters.IterationSetupAction,
125-
engineParameters.IterationCleanupAction,
126-
engineParameters.OperationsPerInvoke,
127-
engineParameters.MeasureExtraStats,
128-
engineParameters.BenchmarkName);
12914
}
13015
}

0 commit comments

Comments
 (0)