@@ -105,14 +105,41 @@ public void Dispose()
105105 }
106106 }
107107
108+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
109+ private IEnumerable < ( IterationStage stage , IterationMode mode , IEngineStageEvaluator evaluator ) > EnumerateStages ( )
110+ {
111+ if ( Strategy != RunStrategy . ColdStart )
112+ {
113+ if ( Strategy != RunStrategy . Monitoring )
114+ {
115+ var pilotEvaluator = pilotStage . GetEvaluator ( ) ;
116+ if ( pilotEvaluator != null )
117+ {
118+ yield return ( IterationStage . Pilot , IterationMode . Workload , pilotEvaluator ) ;
119+ }
120+
121+ if ( EvaluateOverhead )
122+ {
123+ yield return ( IterationStage . Warmup , IterationMode . Overhead , warmupStage . GetOverheadEvaluator ( ) ) ;
124+ yield return ( IterationStage . Actual , IterationMode . Overhead , actualStage . GetOverheadEvaluator ( ) ) ;
125+ }
126+ }
127+
128+ yield return ( IterationStage . Warmup , IterationMode . Workload , warmupStage . GetWorkloadEvaluator ( Strategy ) ) ;
129+ }
130+
131+ Host . BeforeMainRun ( ) ;
132+
133+ yield return ( IterationStage . Actual , IterationMode . Workload , actualStage . GetWorkloadEvaluator ( Strategy == RunStrategy . Monitoring ) ) ;
134+
135+ Host . AfterMainRun ( ) ;
136+ }
137+
108138 // AggressiveOptimization forces the method to go straight to tier1 JIT, and will never be re-jitted,
109139 // eliminating tiered JIT as a potential variable in measurements.
110140 [ MethodImpl ( CodeGenHelper . AggressiveOptimizationOption ) ]
111141 public RunResults Run ( )
112142 {
113- // This method is huge, because all stages are inlined. This ensures the stack size
114- // remains constant for each benchmark invocation, eliminating stack sizes as a potential variable in measurements.
115- // #1120
116143 var measurements = new List < Measurement > ( ) ;
117144 measurements . AddRange ( jittingMeasurements ) ;
118145
@@ -121,191 +148,23 @@ public RunResults Run()
121148 if ( EngineEventSource . Log . IsEnabled ( ) )
122149 EngineEventSource . Log . BenchmarkStart ( BenchmarkName ) ;
123150
124- if ( Strategy != RunStrategy . ColdStart )
125- {
126- if ( Strategy != RunStrategy . Monitoring )
127- {
128- // Pilot Stage
129- {
130- // If InvocationCount is specified, pilot stage should be skipped
131- if ( TargetJob . HasValue ( RunMode . InvocationCountCharacteristic ) )
132- {
133- }
134- // Here we want to guess "perfect" amount of invocation
135- else if ( TargetJob . HasValue ( RunMode . IterationTimeCharacteristic ) )
136- {
137- // Perfect invocation count
138- invokeCount = pilotStage . Autocorrect ( MinInvokeCount ) ;
139-
140- int iterationCounter = 0 ;
141-
142- int downCount = 0 ; // Amount of iterations where newInvokeCount < invokeCount
143- while ( true )
144- {
145- iterationCounter ++ ;
146- var measurement = RunIteration ( new IterationData ( IterationMode . Workload , IterationStage . Pilot , iterationCounter , invokeCount , UnrollFactor ) ) ;
147- measurements . Add ( measurement ) ;
148- double actualIterationTime = measurement . Nanoseconds ;
149- long newInvokeCount = pilotStage . Autocorrect ( Math . Max ( pilotStage . minInvokeCount , ( long ) Math . Round ( invokeCount * pilotStage . targetIterationTime / actualIterationTime ) ) ) ;
150-
151- if ( newInvokeCount < invokeCount )
152- downCount ++ ;
153-
154- if ( Math . Abs ( newInvokeCount - invokeCount ) <= 1 || downCount >= 3 )
155- break ;
156-
157- invokeCount = newInvokeCount ;
158- }
159- WriteLine ( ) ;
160- }
161- else
162- {
163- // A case where we don't have specific iteration time.
164- invokeCount = pilotStage . Autocorrect ( pilotStage . minInvokeCount ) ;
165-
166- int iterationCounter = 0 ;
167- while ( true )
168- {
169- iterationCounter ++ ;
170- var measurement = RunIteration ( new IterationData ( IterationMode . Workload , IterationStage . Pilot , iterationCounter , invokeCount , UnrollFactor ) ) ;
171- measurements . Add ( measurement ) ;
172- double iterationTime = measurement . Nanoseconds ;
173- double operationError = 2.0 * pilotStage . resolution / invokeCount ; // An operation error which has arisen due to the Chronometer precision
174-
175- // Max acceptable operation error
176- double operationMaxError1 = iterationTime / invokeCount * pilotStage . maxRelativeError ;
177- double operationMaxError2 = pilotStage . maxAbsoluteError ? . Nanoseconds ?? double . MaxValue ;
178- double operationMaxError = Math . Min ( operationMaxError1 , operationMaxError2 ) ;
179-
180- bool isFinished = operationError < operationMaxError && iterationTime >= pilotStage . minIterationTime . Nanoseconds ;
181- if ( isFinished )
182- break ;
183- if ( invokeCount >= EnginePilotStage . MaxInvokeCount )
184- break ;
185-
186- if ( UnrollFactor == 1 && invokeCount < EnvironmentResolver . DefaultUnrollFactorForThroughput )
187- invokeCount += 1 ;
188- else
189- invokeCount *= 2 ;
190- }
191- WriteLine ( ) ;
192- }
193- }
194- // End Pilot Stage
195-
196- if ( EvaluateOverhead )
197- {
198- // Warmup Overhead
199- {
200- var warmupMeasurements = new List < Measurement > ( ) ;
201-
202- var criteria = DefaultStoppingCriteriaFactory . Instance . CreateWarmup ( TargetJob , Resolver , IterationMode . Overhead , RunStrategy . Throughput ) ;
203- int iterationCounter = 0 ;
204- while ( ! criteria . Evaluate ( warmupMeasurements ) . IsFinished )
205- {
206- iterationCounter ++ ;
207- warmupMeasurements . Add ( RunIteration ( new IterationData ( IterationMode . Overhead , IterationStage . Warmup , iterationCounter , invokeCount , UnrollFactor ) ) ) ;
208- }
209- WriteLine ( ) ;
210-
211- measurements . AddRange ( warmupMeasurements ) ;
212- }
213- // End Warmup Overhead
214-
215- // Actual Overhead
216- {
217- var measurementsForStatistics = new List < Measurement > ( actualStage . maxIterationCount ) ;
218-
219- int iterationCounter = 0 ;
220- double effectiveMaxRelativeError = EngineActualStage . MaxOverheadRelativeError ;
221- while ( true )
222- {
223- iterationCounter ++ ;
224- var measurement = RunIteration ( new IterationData ( IterationMode . Overhead , IterationStage . Actual , iterationCounter , invokeCount , UnrollFactor ) ) ;
225- measurements . Add ( measurement ) ;
226- measurementsForStatistics . Add ( measurement ) ;
227-
228- var statistics = MeasurementsStatistics . Calculate ( measurementsForStatistics , actualStage . outlierMode ) ;
229- double actualError = statistics . LegacyConfidenceInterval . Margin ;
230-
231- double maxError1 = effectiveMaxRelativeError * statistics . Mean ;
232- double maxError2 = actualStage . maxAbsoluteError ? . Nanoseconds ?? double . MaxValue ;
233- double maxError = Math . Min ( maxError1 , maxError2 ) ;
234-
235- if ( iterationCounter >= actualStage . minIterationCount && actualError < maxError )
236- break ;
237-
238- if ( iterationCounter >= actualStage . maxIterationCount || iterationCounter >= EngineActualStage . MaxOverheadIterationCount )
239- break ;
240- }
241- WriteLine ( ) ;
242- }
243- // End Actual Overhead
244- }
245- }
246-
247- // Warmup Workload
248- {
249- var workloadMeasurements = new List < Measurement > ( ) ;
250-
251- var criteria = DefaultStoppingCriteriaFactory . Instance . CreateWarmup ( TargetJob , Resolver , IterationMode . Workload , Strategy ) ;
252- int iterationCounter = 0 ;
253- while ( ! criteria . Evaluate ( workloadMeasurements ) . IsFinished )
254- {
255- iterationCounter ++ ;
256- workloadMeasurements . Add ( RunIteration ( new IterationData ( IterationMode . Workload , IterationStage . Warmup , iterationCounter , invokeCount , UnrollFactor ) ) ) ;
257- }
258- WriteLine ( ) ;
259-
260- measurements . AddRange ( workloadMeasurements ) ;
261- }
262- // End Warmup Workload
263- }
264-
265- Host . BeforeMainRun ( ) ;
151+ // Enumerate the stages and run iterations in a loop to ensure each benchmark invocation is called with a constant stack size.
152+ // #1120
153+ foreach ( var ( stage , mode , evaluator ) in EnumerateStages ( ) )
154+ {
155+ var stageMeasurements = new List < Measurement > ( evaluator . MaxIterationCount ) ;
156+ int iterationCounter = 0 ;
157+ while ( ! evaluator . EvaluateShouldStop ( stageMeasurements , ref invokeCount ) )
158+ {
159+ // TODO: Not sure why index is 1-based? 0-based is standard.
160+ ++ iterationCounter ;
161+ var measurement = RunIteration ( new IterationData ( mode , stage , iterationCounter , invokeCount , UnrollFactor ) ) ;
162+ stageMeasurements . Add ( measurement ) ;
163+ }
164+ measurements . AddRange ( stageMeasurements ) ;
266165
267- // Actual Workload
268- {
269- if ( actualStage . iterationCount == null && Strategy != RunStrategy . Monitoring )
270- {
271- // RunAuto
272- var measurementsForStatistics = new List < Measurement > ( actualStage . maxIterationCount ) ;
273-
274- int iterationCounter = 0 ;
275- double effectiveMaxRelativeError = actualStage . maxRelativeError ;
276- while ( true )
277- {
278- iterationCounter ++ ;
279- var measurement = RunIteration ( new IterationData ( IterationMode . Workload , IterationStage . Actual , iterationCounter , invokeCount , UnrollFactor ) ) ;
280- measurements . Add ( measurement ) ;
281- measurementsForStatistics . Add ( measurement ) ;
282-
283- var statistics = MeasurementsStatistics . Calculate ( measurementsForStatistics , actualStage . outlierMode ) ;
284- double actualError = statistics . LegacyConfidenceInterval . Margin ;
285-
286- double maxError1 = effectiveMaxRelativeError * statistics . Mean ;
287- double maxError2 = actualStage . maxAbsoluteError ? . Nanoseconds ?? double . MaxValue ;
288- double maxError = Math . Min ( maxError1 , maxError2 ) ;
289-
290- if ( iterationCounter >= actualStage . minIterationCount && actualError < maxError )
291- break ;
292-
293- if ( iterationCounter >= actualStage . maxIterationCount )
294- break ;
295- }
296- }
297- else
298- {
299- // RunSpecific
300- var iterationCount = actualStage . iterationCount ?? EngineActualStage . DefaultWorkloadCount ;
301- for ( int i = 0 ; i < iterationCount ; i ++ )
302- measurements . Add ( RunIteration ( new IterationData ( IterationMode . Workload , IterationStage . Actual , i + 1 , invokeCount , UnrollFactor ) ) ) ;
303- }
304166 WriteLine ( ) ;
305167 }
306- // End Actual Workload
307-
308- Host . AfterMainRun ( ) ;
309168
310169 ( GcStats workGcHasDone , ThreadingStats threadingStats , double exceptionFrequency ) = includeExtraStats
311170 ? GetExtraStats ( new IterationData ( IterationMode . Workload , IterationStage . Actual , 0 , invokeCount , UnrollFactor ) )
0 commit comments