Skip to content

Commit 8a79815

Browse files
committed
Refactored engine JIT stage.
1 parent 604ff55 commit 8a79815

27 files changed

Lines changed: 917 additions & 637 deletions

src/BenchmarkDotNet/Engines/Engine.cs

Lines changed: 77 additions & 104 deletions
Large diffs are not rendered by default.
Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3-
using BenchmarkDotNet.Characteristics;
3+
using System.Diagnostics;
44
using BenchmarkDotNet.Jobs;
55
using BenchmarkDotNet.Mathematics;
66
using BenchmarkDotNet.Reports;
@@ -9,22 +9,31 @@
99

1010
namespace BenchmarkDotNet.Engines
1111
{
12-
internal abstract class EngineActualStage(IterationMode iterationMode) : EngineStage(IterationStage.Actual, iterationMode)
12+
internal abstract class EngineActualStage(IterationMode iterationMode, long invokeCount, int unrollFactor, EngineParameters parameters) : EngineStage(IterationStage.Actual, iterationMode, parameters)
1313
{
1414
internal const int MaxOverheadIterationCount = 20;
1515

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

19-
internal static EngineActualStage GetWorkload(IEngine engine, RunStrategy strategy)
19+
internal static EngineActualStage GetOverhead(long invokeCount, int unrollFactor, EngineParameters parameters)
20+
=> new EngineActualStageAuto(IterationMode.Overhead, invokeCount, unrollFactor, parameters);
21+
22+
internal static EngineActualStage GetWorkload(RunStrategy strategy, long invokeCount, int unrollFactor, EngineParameters parameters)
2023
{
21-
var targetJob = engine.TargetJob;
24+
var targetJob = parameters.TargetJob;
2225
int? iterationCount = targetJob.ResolveValueAsNullable(RunMode.IterationCountCharacteristic);
2326
const int DefaultWorkloadCount = 10;
2427
return iterationCount == null && strategy != RunStrategy.Monitoring
25-
? new EngineActualStageAuto(targetJob, engine.Resolver, IterationMode.Workload)
26-
: new EngineActualStageSpecific(iterationCount ?? DefaultWorkloadCount, IterationMode.Workload);
28+
? new EngineActualStageAuto(IterationMode.Workload, invokeCount, unrollFactor, parameters)
29+
: new EngineActualStageSpecific(iterationCount ?? DefaultWorkloadCount, IterationMode.Workload, invokeCount, unrollFactor, parameters);
2730
}
31+
32+
protected IterationData GetIterationData()
33+
=> new(Mode, Stage, ++iterationIndex, invokeCount, unrollFactor, parameters.IterationSetupAction, parameters.IterationCleanupAction,
34+
Mode == IterationMode.Workload
35+
? unrollFactor == 1 ? parameters.WorkloadActionNoUnroll : parameters.WorkloadActionUnroll
36+
: unrollFactor == 1 ? parameters.OverheadActionNoUnroll : parameters.OverheadActionUnroll);
2837
}
2938

3039
internal sealed class EngineActualStageAuto : EngineActualStage
@@ -37,22 +46,23 @@ internal sealed class EngineActualStageAuto : EngineActualStage
3746
private readonly List<Measurement> measurementsForStatistics;
3847
private int iterationCounter = 0;
3948

40-
public EngineActualStageAuto(Job targetJob, IResolver resolver, IterationMode iterationMode) : base(iterationMode)
49+
public EngineActualStageAuto(IterationMode iterationMode, long invokeCount, int unrollFactor, EngineParameters parameters) : base(iterationMode, invokeCount, unrollFactor, parameters)
4150
{
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);
51+
maxRelativeError = parameters.TargetJob.ResolveValue(AccuracyMode.MaxRelativeErrorCharacteristic, parameters.Resolver);
52+
maxAbsoluteError = parameters.TargetJob.ResolveValueAsNullable(AccuracyMode.MaxAbsoluteErrorCharacteristic);
53+
outlierMode = parameters.TargetJob.ResolveValue(AccuracyMode.OutlierModeCharacteristic, parameters.Resolver);
54+
minIterationCount = parameters.TargetJob.ResolveValue(RunMode.MinIterationCountCharacteristic, parameters.Resolver);
55+
maxIterationCount = parameters.TargetJob.ResolveValue(RunMode.MaxIterationCountCharacteristic, parameters.Resolver);
4756
measurementsForStatistics = GetMeasurementList();
4857
}
4958

5059
internal override List<Measurement> GetMeasurementList() => new(maxIterationCount);
5160

52-
internal override bool GetShouldRunIteration(List<Measurement> measurements, ref long invokeCount)
61+
internal override bool GetShouldRunIteration(List<Measurement> measurements, out IterationData iterationData)
5362
{
5463
if (measurements.Count == 0)
5564
{
65+
iterationData = GetIterationData();
5666
return true;
5767
}
5868

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

7383
if (iterationCounter >= minIterationCount && actualError < maxError)
7484
{
85+
iterationData = default;
7586
return false;
7687
}
7788

7889
if (iterationCounter >= maxIterationCount || isOverhead && iterationCounter >= MaxOverheadIterationCount)
7990
{
91+
iterationData = default;
8092
return false;
8193
}
8294

95+
iterationData = GetIterationData();
8396
return true;
8497
}
8598
}
8699

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

93-
internal override bool GetShouldRunIteration(List<Measurement> measurements, ref long invokeCount)
94-
=> ++iterationCount <= maxIterationCount;
105+
internal override bool GetShouldRunIteration(List<Measurement> measurements, out IterationData iterationData)
106+
{
107+
if (iterationIndex < maxIterationCount)
108+
{
109+
iterationData = GetIterationData();
110+
return true;
111+
}
112+
113+
iterationData = default;
114+
return false;
115+
}
95116
}
96117
}
Lines changed: 2 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,13 @@
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-
7+
// TODO: Move GlobalSetup/Cleanup to Engine.Run.
288
engineParameters.GlobalSetupAction?.Invoke(); // whatever the settings are, we MUST call global setup here, the global cleanup is part of Engine's Dispose
299

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;
10+
return new Engine(engineParameters);
8011
}
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
88-
89-
engine.Dummy2Action.Invoke();
90-
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);
98-
}
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);
12912
}
13013
}

0 commit comments

Comments
 (0)