Skip to content

Commit cc5834e

Browse files
committed
don't execute long operations more than once per iteration, #736
1 parent 4665ec5 commit cc5834e

File tree

11 files changed

+198
-72
lines changed

11 files changed

+198
-72
lines changed

src/BenchmarkDotNet/Engines/Engine.cs

+1-14
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ public class Engine : IEngine
4141
private readonly EngineWarmupStage warmupStage;
4242
private readonly EngineTargetStage targetStage;
4343
private readonly bool includeMemoryStats;
44-
private bool isJitted;
4544

4645
internal Engine(
4746
IHost host,
@@ -78,22 +77,10 @@ internal Engine(
7877
targetStage = new EngineTargetStage(this);
7978
}
8079

81-
public void Jitting()
82-
{
83-
// first signal about jitting is raised from auto-generated Program.cs, look at BenchmarkProgram.txt
84-
Dummy1Action.Invoke();
85-
MainAction.Invoke(1);
86-
Dummy2Action.Invoke();
87-
IdleAction.Invoke(1);
88-
Dummy3Action.Invoke();
89-
isJitted = true;
90-
}
80+
public void Dispose() => GlobalCleanupAction?.Invoke();
9181

9282
public RunResults Run()
9383
{
94-
if (Strategy.NeedsJitting() != isJitted)
95-
throw new Exception($"You must{(Strategy.NeedsJitting() ? "" : " not")} call Jitting() first (Strategy = {Strategy})!");
96-
9784
long invokeCount = InvocationCount;
9885
IReadOnlyList<Measurement> idle = null;
9986

+57-11
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,85 @@
11
using System;
2+
using BenchmarkDotNet.Horology;
3+
using BenchmarkDotNet.Jobs;
4+
using BenchmarkDotNet.Reports;
25

36
namespace BenchmarkDotNet.Engines
47
{
5-
// TODO: Default instance?
68
public class EngineFactory : IEngineFactory
79
{
8-
public IEngine Create(EngineParameters engineParameters)
10+
public IEngine CreateReadyToRun(EngineParameters engineParameters)
911
{
10-
if (engineParameters.MainAction == null)
11-
throw new ArgumentNullException(nameof(engineParameters.MainAction));
12+
if (engineParameters.MainSingleAction == null)
13+
throw new ArgumentNullException(nameof(engineParameters.MainSingleAction));
14+
if (engineParameters.MainMultiAction == null)
15+
throw new ArgumentNullException(nameof(engineParameters.MainMultiAction));
1216
if (engineParameters.Dummy1Action == null)
1317
throw new ArgumentNullException(nameof(engineParameters.Dummy1Action));
1418
if (engineParameters.Dummy2Action == null)
1519
throw new ArgumentNullException(nameof(engineParameters.Dummy2Action));
1620
if (engineParameters.Dummy3Action == null)
1721
throw new ArgumentNullException(nameof(engineParameters.Dummy3Action));
18-
if (engineParameters.IdleAction == null)
19-
throw new ArgumentNullException(nameof(engineParameters.IdleAction));
22+
if (engineParameters.IdleSingleAction == null)
23+
throw new ArgumentNullException(nameof(engineParameters.IdleSingleAction));
24+
if (engineParameters.IdleMultiAction == null)
25+
throw new ArgumentNullException(nameof(engineParameters.IdleMultiAction));
2026
if(engineParameters.TargetJob == null)
2127
throw new ArgumentNullException(nameof(engineParameters.TargetJob));
28+
29+
engineParameters.GlobalSetupAction?.Invoke();
2230

23-
return new Engine(
31+
var needsJitting = engineParameters.TargetJob.ResolveValue(RunMode.RunStrategyCharacteristic, engineParameters.Resolver).NeedsJitting();
32+
if (!needsJitting)
33+
{
34+
// whatever it is, we can not interfere
35+
return CreateEngine(engineParameters, engineParameters.TargetJob, engineParameters.IdleMultiAction, engineParameters.MainMultiAction);
36+
}
37+
38+
var needsPilot = !engineParameters.TargetJob.HasValue(RunMode.InvocationCountCharacteristic);
39+
if (needsPilot)
40+
{
41+
var singleActionEngine = CreateEngine(engineParameters, engineParameters.TargetJob, engineParameters.IdleSingleAction, engineParameters.MainSingleAction);
42+
43+
var iterationTime = engineParameters.Resolver.Resolve(engineParameters.TargetJob, RunMode.IterationTimeCharacteristic);
44+
if (ShouldExecuteOncePerIteration(Jit(singleActionEngine), iterationTime))
45+
{
46+
var reconfiguredJob = engineParameters.TargetJob.WithInvocationCount(1).WithUnrollFactor(1); // todo: consider if we should set the warmup count to 1!
47+
48+
return CreateEngine(engineParameters, reconfiguredJob, engineParameters.IdleSingleAction, engineParameters.MainSingleAction);
49+
}
50+
}
51+
52+
// it's either a job with explicit configuration or not-very time consuming benchmark, just create the engine, Jit and return
53+
var multiActionEngine = CreateEngine(engineParameters, engineParameters.TargetJob, engineParameters.IdleMultiAction, engineParameters.MainMultiAction);
54+
55+
DeadCodeEliminationHelper.KeepAliveWithoutBoxing(Jit(multiActionEngine));
56+
57+
return multiActionEngine;
58+
}
59+
60+
/// <summary>
61+
/// returns true if it takes longer than the desired iteration time (0,5s by default) to execute benchmark once
62+
/// </summary>
63+
private static bool ShouldExecuteOncePerIteration(Measurement jit, TimeInterval iterationTime)
64+
=> TimeInterval.FromNanoseconds(jit.GetAverageNanoseconds()) > iterationTime;
65+
66+
private static Measurement Jit(Engine engine)
67+
=> engine.RunIteration(new IterationData(IterationMode.Jit, index: -1, invokeCount: 1, unrollFactor: 1));
68+
69+
private static Engine CreateEngine(EngineParameters engineParameters, Job job, Action<long> idle, Action<long> main)
70+
=> new Engine(
2471
engineParameters.Host,
2572
engineParameters.Dummy1Action,
2673
engineParameters.Dummy2Action,
2774
engineParameters.Dummy3Action,
28-
engineParameters.IdleAction,
29-
engineParameters.MainAction,
30-
engineParameters.TargetJob,
75+
idle,
76+
main,
77+
job,
3178
engineParameters.GlobalSetupAction,
3279
engineParameters.GlobalCleanupAction,
3380
engineParameters.IterationSetupAction,
3481
engineParameters.IterationCleanupAction,
3582
engineParameters.OperationsPerInvoke,
3683
engineParameters.MeasureGcStats);
37-
}
3884
}
3985
}

src/BenchmarkDotNet/Engines/EngineParameters.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ namespace BenchmarkDotNet.Engines
77
public class EngineParameters
88
{
99
public IHost Host { get; set; }
10-
public Action<long> MainAction { get; set; }
10+
public Action<long> MainSingleAction { get; set; }
11+
public Action<long> MainMultiAction { get; set; }
1112
public Action Dummy1Action { get; set; }
1213
public Action Dummy2Action { get; set; }
1314
public Action Dummy3Action { get; set; }
14-
public Action<long> IdleAction { get; set; }
15+
public Action<long> IdleSingleAction { get; set; }
16+
public Action<long> IdleMultiAction { get; set; }
1517
public Job TargetJob { get; set; } = Job.Default;
1618
public long OperationsPerInvoke { get; set; } = 1;
1719
public Action GlobalSetupAction { get; set; } = null;

src/BenchmarkDotNet/Engines/IEngine.cs

+1-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace BenchmarkDotNet.Engines
88
{
9-
public interface IEngine
9+
public interface IEngine : IDisposable
1010
{
1111
[NotNull]
1212
IHost Host { get; }
@@ -36,12 +36,6 @@ public interface IEngine
3636

3737
Measurement RunIteration(IterationData data);
3838

39-
/// <summary>
40-
/// must perform jitting via warmup calls
41-
/// <remarks>is called after first call to GlobalSetup, from the auto-generated benchmark process</remarks>
42-
/// </summary>
43-
void Jitting();
44-
4539
RunResults Run();
4640
}
4741
}

src/BenchmarkDotNet/Engines/IEngineFactory.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ namespace BenchmarkDotNet.Engines
22
{
33
public interface IEngineFactory
44
{
5-
IEngine Create(EngineParameters engineParameters);
5+
IEngine CreateReadyToRun(EngineParameters engineParameters);
66
}
77
}

src/BenchmarkDotNet/Engines/IterationMode.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ public enum IterationMode
3535
/// <summary>
3636
/// Unknown
3737
/// </summary>
38-
Unknown
38+
Unknown,
39+
40+
/// <summary>
41+
/// executing benchmark for the purpose of JIT wamup
42+
/// </summary>
43+
Jit
3944
}
4045
}

src/BenchmarkDotNet/Templates/BenchmarkType.txt

+6-15
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,14 @@
3434
MeasureGcStats = $MeasureGcStats$
3535
};
3636

37-
var engine = new $EngineFactoryType$().Create(engineParameters);
38-
39-
instance?.globalSetupAction();
40-
instance?.iterationSetupAction();
41-
42-
if (job.ResolveValue(RunMode.RunStrategyCharacteristic, EngineResolver.Instance).NeedsJitting())
43-
engine.Jitting(); // does first call to main action, must be executed after globalSetup() and iterationSetup()!
44-
45-
instance?.iterationCleanupAction();
46-
47-
var results = engine.Run();
48-
49-
instance?.globalCleanupAction();
37+
using (var engine = new $EngineFactoryType$().CreateReadyToRun(engineParameters))
38+
{
39+
var results = engine.Run();
5040

51-
host.ReportResults(results); // printing costs memory, do this after runs
41+
host.ReportResults(results); // printing costs memory, do this after runs
5242

53-
instance.__TrickTheJIT__(); // compile the method for disassembler, but without actual run of the benchmark ;)
43+
instance.__TrickTheJIT__(); // compile the method for disassembler, but without actual run of the benchmark ;)
44+
}
5445
}
5546

5647
public delegate $IdleMethodReturnTypeName$ IdleDelegate($ArgumentsDefinition$);

src/BenchmarkDotNet/Toolchains/InProcess/InProcessRunner.cs

+8-17
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,11 @@ public static void RunCore(IHost host, Benchmark benchmark, BenchmarkActionCodeg
112112
var engineParameters = new EngineParameters
113113
{
114114
Host = host,
115-
MainAction = mainAction.InvokeMultiple,
115+
MainMultiAction = mainAction.InvokeMultiple,
116116
Dummy1Action = dummy1.InvokeSingle,
117117
Dummy2Action = dummy2.InvokeSingle,
118118
Dummy3Action = dummy3.InvokeSingle,
119-
IdleAction = idleAction.InvokeMultiple,
119+
IdleMultiAction = idleAction.InvokeMultiple,
120120
GlobalSetupAction = globalSetupAction.InvokeSingle,
121121
GlobalCleanupAction = globalCleanupAction.InvokeSingle,
122122
IterationSetupAction = iterationSetupAction.InvokeSingle,
@@ -126,23 +126,14 @@ public static void RunCore(IHost host, Benchmark benchmark, BenchmarkActionCodeg
126126
MeasureGcStats = config.HasMemoryDiagnoser()
127127
};
128128

129-
var engine = job
129+
using (var engine = job
130130
.ResolveValue(InfrastructureMode.EngineFactoryCharacteristic, InfrastructureResolver.Instance)
131-
.Create(engineParameters);
132-
133-
globalSetupAction.InvokeSingle();
134-
iterationSetupAction.InvokeSingle();
135-
136-
if (job.ResolveValue(RunMode.RunStrategyCharacteristic, EngineResolver.Instance).NeedsJitting())
137-
engine.Jitting(); // does first call to main action, must be executed after setup()!
138-
139-
iterationCleanupAction.InvokeSingle();
140-
141-
var results = engine.Run();
142-
143-
globalCleanupAction.InvokeSingle();
131+
.CreateReadyToRun(engineParameters))
132+
{
133+
var results = engine.Run();
144134

145-
host.ReportResults(results); // printing costs memory, do this after runs
135+
host.ReportResults(results); // printing costs memory, do this after runs
136+
}
146137
}
147138
}
148139
}

tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void Empty() { }
5454

5555
public class CustomFactory : IEngineFactory
5656
{
57-
public IEngine Create(EngineParameters engineParameters)
57+
public IEngine CreateReadyToRun(EngineParameters engineParameters)
5858
=> new CustomEngine
5959
{
6060
GlobalCleanupAction = engineParameters.GlobalCleanupAction,
@@ -75,6 +75,8 @@ public RunResults Run()
7575
default);
7676
}
7777

78+
public void Dispose() => GlobalCleanupAction?.Invoke();
79+
7880
public IHost Host { get; }
7981
public void WriteLine() { }
8082
public void WriteLine(string line) { }
@@ -87,7 +89,6 @@ public void WriteLine(string line) { }
8789
public IResolver Resolver { get; }
8890

8991
public Measurement RunIteration(IterationData data) { throw new NotImplementedException(); }
90-
public void Jitting() { }
9192
}
9293
}
9394
}

0 commit comments

Comments
 (0)