@@ -19,6 +19,7 @@ public class Engine : IEngine
1919
2020 [ PublicAPI ] public IHost Host { get ; }
2121 [ PublicAPI ] public Action < long > WorkloadAction { get ; }
22+ [ PublicAPI ] public Action WorkloadActionSingleInvoke { get ; }
2223 [ PublicAPI ] public Action Dummy1Action { get ; }
2324 [ PublicAPI ] public Action Dummy2Action { get ; }
2425 [ PublicAPI ] public Action Dummy3Action { get ; }
@@ -45,18 +46,21 @@ public class Engine : IEngine
4546 private readonly EngineActualStage actualStage ;
4647 private readonly bool includeExtraStats , includeSurvivedMemory ;
4748
48- private long _totalMeasuredSurvivedBytes ;
49+ // These must be static since more than one Engine is used.
50+ private static long survivedBytes ;
51+ private static bool survivedBytesMeasured ;
4952 private Func < long > GetTotalBytes { get ; }
5053
5154 internal Engine (
5255 IHost host ,
5356 IResolver resolver ,
54- Action dummy1Action , Action dummy2Action , Action dummy3Action , Action < long > overheadAction , Action < long > workloadAction , Job targetJob ,
57+ Action dummy1Action , Action dummy2Action , Action dummy3Action , Action < long > overheadAction , Action < long > workloadAction , Action workloadActionSingleInvoke , Job targetJob ,
5558 Action globalSetupAction , Action globalCleanupAction , Action iterationSetupAction , Action iterationCleanupAction , long operationsPerInvoke ,
5659 bool includeExtraStats , bool includeSurvivedMemory , string benchmarkName )
5760 {
5861 Host = host ;
5962 OverheadAction = overheadAction ;
63+ WorkloadActionSingleInvoke = workloadActionSingleInvoke ;
6064 Dummy1Action = dummy1Action ;
6165 Dummy2Action = dummy2Action ;
6266 Dummy3Action = dummy3Action ;
@@ -84,14 +88,15 @@ internal Engine(
8488 pilotStage = new EnginePilotStage ( this ) ;
8589 actualStage = new EngineActualStage ( this ) ;
8690
87- GetTotalBytes = GetTotalBytesFunc ( ) ;
88- // Necessary for CORE runtimes.
89- if ( includeSurvivedMemory )
91+ if ( includeSurvivedMemory && ! survivedBytesMeasured )
9092 {
93+ GetTotalBytes = GetTotalBytesFunc ( ) ;
94+
95+ // Necessary for CORE runtimes.
9196 // Measure bytes to allow GC monitor to make its allocations.
9297 GetTotalBytes ( ) ;
9398 // Run the clock once to allow it to make its allocations.
94- MeasureAction ( _ => { } , 0 ) ;
99+ MeasureAction ( ( ) => { } ) ;
95100 GetTotalBytes ( ) ;
96101 }
97102 }
@@ -100,10 +105,8 @@ private Func<long> GetTotalBytesFunc()
100105 {
101106 // Only enable monitoring if memory diagnoser with survived memory is applied.
102107 // Don't try to measure in Mono, Monitoring is not available, and GC.GetTotalMemory is very inaccurate.
103- if ( ! includeSurvivedMemory || RuntimeInformation . IsMono )
104- {
108+ if ( RuntimeInformation . IsMono )
105109 return ( ) => 0 ;
106- }
107110 try
108111 {
109112 // Docs say this should be available in .NET Core 2.1, but it throws an exception.
@@ -194,20 +197,32 @@ public Measurement RunIteration(IterationData data)
194197 bool isOverhead = data . IterationMode == IterationMode . Overhead ;
195198 var action = isOverhead ? OverheadAction : WorkloadAction ;
196199
200+ double nanoseconds = 0 ;
197201 if ( ! isOverhead )
202+ {
198203 IterationSetupAction ( ) ;
199204
205+ if ( includeSurvivedMemory && ! survivedBytesMeasured )
206+ {
207+ // Measure survived bytes for only the first invocation.
208+ survivedBytesMeasured = true ;
209+ ++ totalOperations ;
210+ long beforeBytes = GetTotalBytes ( ) ;
211+ nanoseconds = MeasureAction ( WorkloadActionSingleInvoke ) ;
212+ long afterBytes = GetTotalBytes ( ) ;
213+ survivedBytes = afterBytes - beforeBytes ;
214+ }
215+ }
216+
200217 GcCollect ( ) ;
201218
202219 if ( EngineEventSource . Log . IsEnabled ( ) )
203220 EngineEventSource . Log . IterationStart ( data . IterationMode , data . IterationStage , totalOperations ) ;
204221
205222 // Measure
206- long beforeBytes = GetTotalBytes ( ) ;
207- double nanoseconds = MeasureAction ( action , invokeCount / unrollFactor ) ;
208- long afterBytes = GetTotalBytes ( ) ;
209- long survivedBytes = afterBytes - beforeBytes ;
210- _totalMeasuredSurvivedBytes += survivedBytes ;
223+ var clock = Clock . Start ( ) ;
224+ action ( invokeCount / unrollFactor ) ;
225+ nanoseconds += clock . GetElapsed ( ) . GetNanoseconds ( ) ;
211226
212227 if ( EngineEventSource . Log . IsEnabled ( ) )
213228 EngineEventSource . Log . IterationStop ( data . IterationMode , data . IterationStage , totalOperations ) ;
@@ -218,18 +233,18 @@ public Measurement RunIteration(IterationData data)
218233 GcCollect ( ) ;
219234
220235 // Results
221- var measurement = new Measurement ( 0 , data . IterationMode , data . IterationStage , data . Index , totalOperations , nanoseconds , survivedBytes ) ;
236+ var measurement = new Measurement ( 0 , data . IterationMode , data . IterationStage , data . Index , totalOperations , nanoseconds ) ;
222237 WriteLine ( measurement . ToString ( ) ) ;
223238
224239 return measurement ;
225240 }
226241
227242 // This is necessary for the CORE runtime to clean up the memory from the clock.
228243 [ MethodImpl ( MethodImplOptions . NoInlining ) ]
229- private double MeasureAction ( Action < long > action , long arg )
244+ private double MeasureAction ( Action action )
230245 {
231246 var clock = Clock . Start ( ) ;
232- action ( arg ) ;
247+ action ( ) ;
233248 return clock . GetElapsed ( ) . GetNanoseconds ( ) ;
234249 }
235250
@@ -252,7 +267,7 @@ private double MeasureAction(Action<long> action, long arg)
252267
253268 IterationCleanupAction ( ) ; // we run iteration cleanup after collecting GC stats
254269
255- GcStats gcStats = ( finalGcStats - initialGcStats ) . WithTotalOperationsAndSurvivedBytes ( data . InvokeCount * OperationsPerInvoke , _totalMeasuredSurvivedBytes ) ;
270+ GcStats gcStats = ( finalGcStats - initialGcStats ) . WithTotalOperationsAndSurvivedBytes ( data . InvokeCount * OperationsPerInvoke , survivedBytes ) ;
256271 ThreadingStats threadingStats = ( finalThreadingStats - initialThreadingStats ) . WithTotalOperations ( data . InvokeCount * OperationsPerInvoke ) ;
257272
258273 return ( gcStats , threadingStats ) ;
0 commit comments