Skip to content

Commit a9664f4

Browse files
authored
don't execute long operations more than once per iteration (#760), fixes #736
* don't execute long operations more than once per iteration, #736 * generate the right C# code for #736 * EngineParameters.Resolver was always null or ignored ;), #736 * don't forget to JIT idle, #736 * do the math right for unroll factor for JIT, #736 * generate the right IL code, #736 * Setup and Cleanup are jitted together with benchmark, #736 * engine factory is now supposed to create an engine which is ready to run (hence the method name change), #736 * addressing PR feedback, #736 * bring back the calls to DummyActions, #736 * align iteration mode * don't measure the overhead for time consuming benchmarks, don't run pilot if jitting gives the answer, #736 * fix the Linux build
1 parent 6f693e0 commit a9664f4

File tree

20 files changed

+461
-128
lines changed

20 files changed

+461
-128
lines changed

samples/BenchmarkDotNet.Samples/Intro/IntroInProcessWrongEnv.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using BenchmarkDotNet.Environments;
66
using BenchmarkDotNet.Jobs;
77
using BenchmarkDotNet.Order;
8+
using BenchmarkDotNet.Portability;
89
using BenchmarkDotNet.Toolchains.InProcess;
910

1011
namespace BenchmarkDotNet.Samples.Intro
@@ -18,7 +19,7 @@ private class Config : ManualConfig
1819
{
1920
public Config()
2021
{
21-
var wrongPlatform = IntPtr.Size == sizeof(int)
22+
var wrongPlatform = RuntimeInformation.GetCurrentPlatform() == Platform.X86
2223
? Platform.X64
2324
: Platform.X86;
2425

src/BenchmarkDotNet/Diagnosers/WindowsDisassembler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public static bool Is64Bit(Process process)
156156
return !isWow64;
157157
}
158158

159-
return IntPtr.Size == 8; // todo: find the way to cover all scenarios for .NET Core
159+
return Portability.RuntimeInformation.GetCurrentPlatform() == Platform.X64; // todo: find the way to cover all scenarios for .NET Core
160160
}
161161

162162
[DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]

src/BenchmarkDotNet/Engines/Engine.cs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ 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,
47+
IResolver resolver,
4848
Action dummy1Action, Action dummy2Action, Action dummy3Action, Action<long> idleAction, Action<long> mainAction, Job targetJob,
4949
Action globalSetupAction, Action globalCleanupAction, Action iterationSetupAction, Action iterationCleanupAction, long operationsPerInvoke,
5050
bool includeMemoryStats)
@@ -64,7 +64,7 @@ internal Engine(
6464
OperationsPerInvoke = operationsPerInvoke;
6565
this.includeMemoryStats = includeMemoryStats;
6666

67-
Resolver = new CompositeResolver(BenchmarkRunner.DefaultResolver, EngineResolver.Instance);
67+
Resolver = resolver;
6868

6969
Clock = targetJob.ResolveValue(InfrastructureMode.ClockCharacteristic, Resolver);
7070
ForceAllocations = targetJob.ResolveValue(GcMode.ForceCharacteristic, Resolver);
@@ -78,22 +78,10 @@ internal Engine(
7878
targetStage = new EngineTargetStage(this);
7979
}
8080

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-
}
81+
public void Dispose() => GlobalCleanupAction?.Invoke();
9182

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

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,114 @@
11
using System;
2+
using BenchmarkDotNet.Horology;
3+
using BenchmarkDotNet.Jobs;
4+
using BenchmarkDotNet.Reports;
5+
using BenchmarkDotNet.Running;
26

37
namespace BenchmarkDotNet.Engines
48
{
5-
// TODO: Default instance?
69
public class EngineFactory : IEngineFactory
710
{
8-
public IEngine Create(EngineParameters engineParameters)
11+
public IEngine CreateReadyToRun(EngineParameters engineParameters)
912
{
10-
if (engineParameters.MainAction == null)
11-
throw new ArgumentNullException(nameof(engineParameters.MainAction));
13+
if (engineParameters.MainSingleAction == null)
14+
throw new ArgumentNullException(nameof(engineParameters.MainSingleAction));
15+
if (engineParameters.MainMultiAction == null)
16+
throw new ArgumentNullException(nameof(engineParameters.MainMultiAction));
1217
if (engineParameters.Dummy1Action == null)
1318
throw new ArgumentNullException(nameof(engineParameters.Dummy1Action));
1419
if (engineParameters.Dummy2Action == null)
1520
throw new ArgumentNullException(nameof(engineParameters.Dummy2Action));
1621
if (engineParameters.Dummy3Action == null)
1722
throw new ArgumentNullException(nameof(engineParameters.Dummy3Action));
18-
if (engineParameters.IdleAction == null)
19-
throw new ArgumentNullException(nameof(engineParameters.IdleAction));
23+
if (engineParameters.IdleSingleAction == null)
24+
throw new ArgumentNullException(nameof(engineParameters.IdleSingleAction));
25+
if (engineParameters.IdleMultiAction == null)
26+
throw new ArgumentNullException(nameof(engineParameters.IdleMultiAction));
2027
if(engineParameters.TargetJob == null)
2128
throw new ArgumentNullException(nameof(engineParameters.TargetJob));
2229

23-
return new Engine(
30+
engineParameters.GlobalSetupAction?.Invoke(); // whatever the settings are, we MUST call global setup here, the global cleanup is part of Engine's Dispose
31+
32+
if (!engineParameters.NeedsJitting) // just create the engine, do NOT jit
33+
return CreateMultiActionEngine(engineParameters);
34+
35+
int jitIndex = 0;
36+
37+
if (engineParameters.HasInvocationCount || engineParameters.HasUnrollFactor) // it's a job with explicit configuration, just create the engine and jit it
38+
{
39+
var warmedUpMultiActionEngine = CreateMultiActionEngine(engineParameters);
40+
41+
DeadCodeEliminationHelper.KeepAliveWithoutBoxing(Jit(warmedUpMultiActionEngine, ++jitIndex, invokeCount: engineParameters.UnrollFactor, unrollFactor: engineParameters.UnrollFactor));
42+
43+
return warmedUpMultiActionEngine;
44+
}
45+
46+
var singleActionEngine = CreateSingleActionEngine(engineParameters);
47+
if (Jit(singleActionEngine, ++jitIndex, invokeCount: 1, unrollFactor: 1) > engineParameters.IterationTime)
48+
return singleActionEngine; // executing once takes longer than iteration time => long running benchmark, needs no pilot and no overhead
49+
50+
var multiActionEngine = CreateMultiActionEngine(engineParameters);
51+
int defaultUnrollFactor = Job.Default.ResolveValue(RunMode.UnrollFactorCharacteristic, EngineParameters.DefaultResolver);
52+
53+
if (Jit(multiActionEngine, ++jitIndex, invokeCount: defaultUnrollFactor, unrollFactor: defaultUnrollFactor) > engineParameters.IterationTime)
54+
{ // executing defaultUnrollFactor times takes longer than iteration time => medium running benchmark, needs no pilot and no overhead
55+
var defaultUnrollFactorTimesPerIterationNoPilotNoOverhead = CreateJobWhichDoesNotNeedPilotAndOverheadEvaluation(engineParameters.TargetJob,
56+
invocationCount: defaultUnrollFactor, unrollFactor: defaultUnrollFactor); // run the benchmark exactly once per iteration
57+
58+
return CreateEngine(engineParameters, defaultUnrollFactorTimesPerIterationNoPilotNoOverhead, engineParameters.IdleMultiAction, engineParameters.MainMultiAction);
59+
}
60+
61+
return multiActionEngine;
62+
}
63+
64+
/// <returns>the time it took to run the benchmark</returns>
65+
private static TimeInterval Jit(Engine engine, int jitIndex, int invokeCount, int unrollFactor)
66+
{
67+
engine.Dummy1Action.Invoke();
68+
69+
DeadCodeEliminationHelper.KeepAliveWithoutBoxing(engine.RunIteration(new IterationData(IterationMode.IdleJitting, jitIndex, invokeCount, unrollFactor))); // don't forget to JIT idle
70+
71+
engine.Dummy2Action.Invoke();
72+
73+
var result = engine.RunIteration(new IterationData(IterationMode.MainJitting, jitIndex, invokeCount, unrollFactor));
74+
75+
engine.Dummy3Action.Invoke();
76+
77+
engine.WriteLine();
78+
79+
return TimeInterval.FromNanoseconds(result.Nanoseconds);
80+
}
81+
82+
private static Engine CreateMultiActionEngine(EngineParameters engineParameters)
83+
=> CreateEngine(engineParameters, engineParameters.TargetJob, engineParameters.IdleMultiAction, engineParameters.MainMultiAction);
84+
85+
private static Engine CreateSingleActionEngine(EngineParameters engineParameters)
86+
=> CreateEngine(engineParameters,
87+
CreateJobWhichDoesNotNeedPilotAndOverheadEvaluation(engineParameters.TargetJob, invocationCount: 1, unrollFactor: 1), // run the benchmark exactly once per iteration
88+
engineParameters.IdleSingleAction,
89+
engineParameters.MainSingleAction);
90+
91+
private static Job CreateJobWhichDoesNotNeedPilotAndOverheadEvaluation(Job sourceJob, int invocationCount, int unrollFactor)
92+
=> sourceJob
93+
.WithInvocationCount(invocationCount).WithUnrollFactor(unrollFactor)
94+
.WithEvaluateOverhead(false); // it's very time consuming, don't evaluate the overhead which would be 0,000025% of the target run or even less
95+
// todo: consider if we should set the warmup count to 2
96+
97+
private static Engine CreateEngine(EngineParameters engineParameters, Job job, Action<long> idle, Action<long> main)
98+
=> new Engine(
2499
engineParameters.Host,
100+
EngineParameters.DefaultResolver,
25101
engineParameters.Dummy1Action,
26102
engineParameters.Dummy2Action,
27103
engineParameters.Dummy3Action,
28-
engineParameters.IdleAction,
29-
engineParameters.MainAction,
30-
engineParameters.TargetJob,
104+
idle,
105+
main,
106+
job,
31107
engineParameters.GlobalSetupAction,
32108
engineParameters.GlobalCleanupAction,
33109
engineParameters.IterationSetupAction,
34110
engineParameters.IterationCleanupAction,
35111
engineParameters.OperationsPerInvoke,
36112
engineParameters.MeasureGcStats);
37-
}
38113
}
39114
}
Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,39 @@
11
using System;
22
using BenchmarkDotNet.Characteristics;
3+
using BenchmarkDotNet.Horology;
34
using BenchmarkDotNet.Jobs;
5+
using BenchmarkDotNet.Running;
46

57
namespace BenchmarkDotNet.Engines
68
{
79
public class EngineParameters
810
{
11+
public static readonly IResolver DefaultResolver = new CompositeResolver(BenchmarkRunner.DefaultResolver, EngineResolver.Instance);
12+
913
public IHost Host { get; set; }
10-
public Action<long> MainAction { get; set; }
14+
public Action<long> MainSingleAction { get; set; }
15+
public Action<long> MainMultiAction { get; set; }
1116
public Action Dummy1Action { get; set; }
1217
public Action Dummy2Action { get; set; }
1318
public Action Dummy3Action { get; set; }
14-
public Action<long> IdleAction { get; set; }
19+
public Action<long> IdleSingleAction { get; set; }
20+
public Action<long> IdleMultiAction { get; set; }
1521
public Job TargetJob { get; set; } = Job.Default;
1622
public long OperationsPerInvoke { get; set; } = 1;
17-
public Action GlobalSetupAction { get; set; } = null;
18-
public Action GlobalCleanupAction { get; set; } = null;
19-
public Action IterationSetupAction { get; set; } = null;
20-
public Action IterationCleanupAction { get; set; } = null;
21-
public IResolver Resolver { get; set; }
23+
public Action GlobalSetupAction { get; set; }
24+
public Action GlobalCleanupAction { get; set; }
25+
public Action IterationSetupAction { get; set; }
26+
public Action IterationCleanupAction { get; set; }
2227
public bool MeasureGcStats { get; set; }
28+
29+
public bool NeedsJitting => TargetJob.ResolveValue(RunMode.RunStrategyCharacteristic, DefaultResolver).NeedsJitting();
30+
31+
public bool HasInvocationCount => TargetJob.HasValue(RunMode.InvocationCountCharacteristic);
32+
33+
public bool HasUnrollFactor => TargetJob.HasValue(RunMode.UnrollFactorCharacteristic);
34+
35+
public int UnrollFactor => TargetJob.ResolveValue(RunMode.UnrollFactorCharacteristic, DefaultResolver);
36+
37+
public TimeInterval IterationTime => TargetJob.ResolveValue(RunMode.IterationTimeCharacteristic, DefaultResolver);
2338
}
2439
}

src/BenchmarkDotNet/Engines/IEngine.cs

Lines changed: 1 addition & 7 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 6 additions & 1 deletion
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+
MainJitting, IdleJitting
3944
}
4045
}

src/BenchmarkDotNet/Engines/IterationModeExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
public static class IterationModeExtensions
44
{
55
public static bool IsIdle(this IterationMode mode)
6-
=> mode == IterationMode.IdleWarmup || mode == IterationMode.IdleTarget;
6+
=> mode == IterationMode.IdleWarmup || mode == IterationMode.IdleTarget || mode == IterationMode.IdleJitting;
77
}
88
}

src/BenchmarkDotNet/Extensions/ProcessExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using BenchmarkDotNet.Environments;
55
using BenchmarkDotNet.Jobs;
66
using BenchmarkDotNet.Loggers;
7+
using BenchmarkDotNet.Portability;
78
using BenchmarkDotNet.Running;
89
using JetBrains.Annotations;
910

@@ -31,7 +32,7 @@ private static IntPtr FixAffinity(IntPtr processorAffinity)
3132
{
3233
int cpuMask = (1 << Environment.ProcessorCount) - 1;
3334

34-
return IntPtr.Size == sizeof(Int64)
35+
return RuntimeInformation.GetCurrentPlatform() == Platform.X64
3536
? new IntPtr(processorAffinity.ToInt64() & cpuMask)
3637
: new IntPtr(processorAffinity.ToInt32() & cpuMask);
3738
}

0 commit comments

Comments
 (0)