1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Diagnostics ;
3
4
using System . Globalization ;
4
5
using System . Linq ;
5
6
using System . Runtime . CompilerServices ;
6
7
using BenchmarkDotNet . Characteristics ;
8
+ using BenchmarkDotNet . Environments ;
7
9
using BenchmarkDotNet . Jobs ;
10
+ using BenchmarkDotNet . Mathematics ;
8
11
using BenchmarkDotNet . Portability ;
9
12
using BenchmarkDotNet . Reports ;
10
13
using JetBrains . Annotations ;
@@ -15,8 +18,6 @@ namespace BenchmarkDotNet.Engines
15
18
[ UsedImplicitly ]
16
19
public class Engine : IEngine
17
20
{
18
- public const int MinInvokeCount = 4 ;
19
-
20
21
[ PublicAPI ] public IHost Host { get ; }
21
22
[ PublicAPI ] public Action < long > WorkloadAction { get ; }
22
23
[ PublicAPI ] public Action Dummy1Action { get ; }
@@ -41,9 +42,6 @@ public class Engine : IEngine
41
42
private bool MemoryRandomization { get ; }
42
43
43
44
private readonly List < Measurement > jittingMeasurements = new ( 10 ) ;
44
- private readonly EnginePilotStage pilotStage ;
45
- private readonly EngineWarmupStage warmupStage ;
46
- private readonly EngineActualStage actualStage ;
47
45
private readonly bool includeExtraStats ;
48
46
private readonly Random random ;
49
47
@@ -79,10 +77,6 @@ internal Engine(
79
77
EvaluateOverhead = targetJob . ResolveValue ( AccuracyMode . EvaluateOverheadCharacteristic , Resolver ) ;
80
78
MemoryRandomization = targetJob . ResolveValue ( RunMode . MemoryRandomizationCharacteristic , Resolver ) ;
81
79
82
- warmupStage = new EngineWarmupStage ( this ) ;
83
- pilotStage = new EnginePilotStage ( this ) ;
84
- actualStage = new EngineActualStage ( this ) ;
85
-
86
80
random = new Random ( 12345 ) ; // we are using constant seed to try to get repeatable results
87
81
}
88
82
@@ -102,6 +96,9 @@ public void Dispose()
102
96
}
103
97
}
104
98
99
+ // AggressiveOptimization forces the method to go straight to tier1 JIT, and will never be re-jitted,
100
+ // eliminating tiered JIT as a potential variable in measurements.
101
+ [ MethodImpl ( CodeGenHelper . AggressiveOptimizationOption ) ]
105
102
public RunResults Run ( )
106
103
{
107
104
var measurements = new List < Measurement > ( ) ;
@@ -112,30 +109,34 @@ public RunResults Run()
112
109
if ( EngineEventSource . Log . IsEnabled ( ) )
113
110
EngineEventSource . Log . BenchmarkStart ( BenchmarkName ) ;
114
111
115
- if ( Strategy != RunStrategy . ColdStart )
116
- {
117
- if ( Strategy != RunStrategy . Monitoring )
118
- {
119
- var pilotStageResult = pilotStage . Run ( ) ;
120
- invokeCount = pilotStageResult . PerfectInvocationCount ;
121
- measurements . AddRange ( pilotStageResult . Measurements ) ;
122
-
123
- if ( EvaluateOverhead )
124
- {
125
- measurements . AddRange ( warmupStage . RunOverhead ( invokeCount , UnrollFactor ) ) ;
126
- measurements . AddRange ( actualStage . RunOverhead ( invokeCount , UnrollFactor ) ) ;
127
- }
112
+ // Enumerate the stages and run iterations in a loop to ensure each benchmark invocation is called with a constant stack size.
113
+ // #1120
114
+ foreach ( var stage in EngineStage . EnumerateStages ( this , Strategy , EvaluateOverhead ) )
115
+ {
116
+ if ( stage . Stage == IterationStage . Actual && stage . Mode == IterationMode . Workload )
117
+ {
118
+ Host . BeforeMainRun ( ) ;
119
+ }
120
+
121
+ var stageMeasurements = stage . GetMeasurementList ( ) ;
122
+ // 1-based iterationIndex
123
+ int iterationIndex = 1 ;
124
+ while ( stage . GetShouldRunIteration ( stageMeasurements , ref invokeCount ) )
125
+ {
126
+ var measurement = RunIteration ( new IterationData ( stage . Mode , stage . Stage , iterationIndex , invokeCount , UnrollFactor ) ) ;
127
+ stageMeasurements . Add ( measurement ) ;
128
+ ++ iterationIndex ;
129
+ }
130
+ measurements . AddRange ( stageMeasurements ) ;
131
+
132
+ WriteLine ( ) ;
133
+
134
+ if ( stage . Stage == IterationStage . Actual && stage . Mode == IterationMode . Workload )
135
+ {
136
+ Host . AfterMainRun ( ) ;
128
137
}
129
-
130
- measurements . AddRange ( warmupStage . RunWorkload ( invokeCount , UnrollFactor , Strategy ) ) ;
131
138
}
132
139
133
- Host . BeforeMainRun ( ) ;
134
-
135
- measurements . AddRange ( actualStage . RunWorkload ( invokeCount , UnrollFactor , forceSpecific : Strategy == RunStrategy . Monitoring ) ) ;
136
-
137
- Host . AfterMainRun ( ) ;
138
-
139
140
( GcStats workGcHasDone , ThreadingStats threadingStats , double exceptionFrequency ) = includeExtraStats
140
141
? GetExtraStats ( new IterationData ( IterationMode . Workload , IterationStage . Actual , 0 , invokeCount , UnrollFactor ) )
141
142
: ( GcStats . Empty , ThreadingStats . Empty , 0 ) ;
@@ -148,11 +149,15 @@ public RunResults Run()
148
149
return new RunResults ( measurements , outlierMode , workGcHasDone , threadingStats , exceptionFrequency ) ;
149
150
}
150
151
152
+ [ MethodImpl ( CodeGenHelper . AggressiveOptimizationOption ) ]
151
153
public Measurement RunIteration ( IterationData data )
152
154
{
153
155
// Initialization
154
156
long invokeCount = data . InvokeCount ;
155
157
int unrollFactor = data . UnrollFactor ;
158
+ if ( invokeCount % unrollFactor != 0 )
159
+ throw new ArgumentOutOfRangeException ( nameof ( data ) , $ "InvokeCount({ invokeCount } ) should be a multiple of UnrollFactor({ unrollFactor } ).") ;
160
+
156
161
long totalOperations = invokeCount * OperationsPerInvoke ;
157
162
bool isOverhead = data . IterationMode == IterationMode . Overhead ;
158
163
bool randomizeMemory = ! isOverhead && MemoryRandomization ;
@@ -167,7 +172,7 @@ public Measurement RunIteration(IterationData data)
167
172
EngineEventSource . Log . IterationStart ( data . IterationMode , data . IterationStage , totalOperations ) ;
168
173
169
174
var clockSpan = randomizeMemory
170
- ? MeasureWithRandomMemory ( action , invokeCount / unrollFactor )
175
+ ? MeasureWithRandomStack ( action , invokeCount / unrollFactor )
171
176
: Measure ( action , invokeCount / unrollFactor ) ;
172
177
173
178
if ( EngineEventSource . Log . IsEnabled ( ) )
@@ -193,8 +198,8 @@ public Measurement RunIteration(IterationData data)
193
198
// This is in a separate method, because stackalloc can affect code alignment,
194
199
// resulting in unexpected measurements on some AMD cpus,
195
200
// even if the stackalloc branch isn't executed. (#2366)
196
- [ MethodImpl ( MethodImplOptions . NoInlining ) ]
197
- private unsafe ClockSpan MeasureWithRandomMemory ( Action < long > action , long invokeCount )
201
+ [ MethodImpl ( MethodImplOptions . NoInlining | CodeGenHelper . AggressiveOptimizationOption ) ]
202
+ private unsafe ClockSpan MeasureWithRandomStack ( Action < long > action , long invokeCount )
198
203
{
199
204
byte * stackMemory = stackalloc byte [ random . Next ( 32 ) ] ;
200
205
var clockSpan = Measure ( action , invokeCount ) ;
@@ -205,6 +210,7 @@ private unsafe ClockSpan MeasureWithRandomMemory(Action<long> action, long invok
205
210
[ MethodImpl ( MethodImplOptions . NoInlining ) ]
206
211
private unsafe void Consume ( byte * _ ) { }
207
212
213
+ [ MethodImpl ( MethodImplOptions . NoInlining | CodeGenHelper . AggressiveOptimizationOption ) ]
208
214
private ClockSpan Measure ( Action < long > action , long invokeCount )
209
215
{
210
216
var clock = Clock . Start ( ) ;
0 commit comments