@@ -19,6 +19,7 @@ public class Engine : IEngine
19
19
20
20
[ PublicAPI ] public IHost Host { get ; }
21
21
[ PublicAPI ] public Action < long > WorkloadAction { get ; }
22
+ [ PublicAPI ] public Action < long > WorkloadActionNoUnroll { get ; }
22
23
[ PublicAPI ] public Action Dummy1Action { get ; }
23
24
[ PublicAPI ] public Action Dummy2Action { get ; }
24
25
[ PublicAPI ] public Action Dummy3Action { get ; }
@@ -44,19 +45,22 @@ public class Engine : IEngine
44
45
private readonly EnginePilotStage pilotStage ;
45
46
private readonly EngineWarmupStage warmupStage ;
46
47
private readonly EngineActualStage actualStage ;
47
- private readonly bool includeExtraStats ;
48
48
private readonly Random random ;
49
+ private readonly bool includeExtraStats , includeSurvivedMemory ;
50
+
51
+ private long ? survivedBytes ;
52
+ private bool survivedBytesMeasured ;
49
53
50
54
internal Engine (
51
55
IHost host ,
52
56
IResolver resolver ,
53
- 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 < long > workloadActionNoUnroll , Job targetJob ,
54
58
Action globalSetupAction , Action globalCleanupAction , Action iterationSetupAction , Action iterationCleanupAction , long operationsPerInvoke ,
55
- bool includeExtraStats , string benchmarkName )
59
+ bool includeExtraStats , bool includeSurvivedMemory , string benchmarkName )
56
60
{
57
-
58
61
Host = host ;
59
62
OverheadAction = overheadAction ;
63
+ WorkloadActionNoUnroll = workloadActionNoUnroll ;
60
64
Dummy1Action = dummy1Action ;
61
65
Dummy2Action = dummy2Action ;
62
66
Dummy3Action = dummy3Action ;
@@ -69,6 +73,7 @@ internal Engine(
69
73
OperationsPerInvoke = operationsPerInvoke ;
70
74
this . includeExtraStats = includeExtraStats ;
71
75
BenchmarkName = benchmarkName ;
76
+ this . includeSurvivedMemory = includeSurvivedMemory ;
72
77
73
78
Resolver = resolver ;
74
79
@@ -84,6 +89,23 @@ internal Engine(
84
89
actualStage = new EngineActualStage ( this ) ;
85
90
86
91
random = new Random ( 12345 ) ; // we are using constant seed to try to get repeatable results
92
+
93
+ if ( includeSurvivedMemory && GcStats . InitSurvivedBytes ( ) )
94
+ {
95
+ // Measure bytes to allow GC monitor to make its allocations.
96
+ GcStats . GetTotalBytes ( ) ;
97
+ // Run the clock once to allow it to make its allocations.
98
+ MeasureAction ( _ => { } , 0 ) ;
99
+ GcStats . GetTotalBytes ( ) ;
100
+ }
101
+ }
102
+
103
+ internal Engine WithInitialData ( Engine other )
104
+ {
105
+ // Copy the survived bytes from the other engine so we only measure it once.
106
+ survivedBytes = other . survivedBytes ;
107
+ survivedBytesMeasured = other . survivedBytesMeasured ;
108
+ return this ;
87
109
}
88
110
89
111
public void Dispose ( )
@@ -168,10 +190,36 @@ public Measurement RunIteration(IterationData data)
168
190
169
191
Span < byte > stackMemory = randomizeMemory ? stackalloc byte [ random . Next ( 32 ) ] : Span < byte > . Empty ;
170
192
171
- // Measure
172
- var clock = Clock . Start ( ) ;
173
- action ( invokeCount / unrollFactor ) ;
174
- var clockSpan = clock . GetElapsed ( ) ;
193
+ bool needsSurvivedMeasurement = includeSurvivedMemory && ! isOverhead && ! survivedBytesMeasured ;
194
+ double nanoseconds ;
195
+ if ( needsSurvivedMeasurement )
196
+ {
197
+ // Measure survived bytes for only the first invocation.
198
+ survivedBytesMeasured = true ;
199
+ if ( totalOperations == 1 )
200
+ {
201
+ // Measure normal invocation for both survived memory and time.
202
+ long ? beforeBytes = GcStats . GetTotalBytes ( ) ;
203
+ nanoseconds = MeasureAction ( action , invokeCount / unrollFactor ) ;
204
+ long ? afterBytes = GcStats . GetTotalBytes ( ) ;
205
+ survivedBytes = afterBytes - beforeBytes ;
206
+ }
207
+ else
208
+ {
209
+ // Measure a single invocation for survived memory, plus normal invocations for time.
210
+ ++ totalOperations ;
211
+ long ? beforeBytes = GcStats . GetTotalBytes ( ) ;
212
+ nanoseconds = MeasureAction ( WorkloadActionNoUnroll , 1 ) ;
213
+ long ? afterBytes = GcStats . GetTotalBytes ( ) ;
214
+ survivedBytes = afterBytes - beforeBytes ;
215
+ nanoseconds += MeasureAction ( action , invokeCount / unrollFactor ) ;
216
+ }
217
+ }
218
+ else
219
+ {
220
+ // Measure time normally.
221
+ nanoseconds = MeasureAction ( action , invokeCount / unrollFactor ) ;
222
+ }
175
223
176
224
if ( EngineEventSource . Log . IsEnabled ( ) )
177
225
EngineEventSource . Log . IterationStop ( data . IterationMode , data . IterationStage , totalOperations ) ;
@@ -185,7 +233,7 @@ public Measurement RunIteration(IterationData data)
185
233
GcCollect ( ) ;
186
234
187
235
// Results
188
- var measurement = new Measurement ( 0 , data . IterationMode , data . IterationStage , data . Index , totalOperations , clockSpan . GetNanoseconds ( ) ) ;
236
+ var measurement = new Measurement ( 0 , data . IterationMode , data . IterationStage , data . Index , totalOperations , nanoseconds ) ;
189
237
WriteLine ( measurement . ToString ( ) ) ;
190
238
if ( measurement . IterationStage == IterationStage . Jitting )
191
239
jittingMeasurements . Add ( measurement ) ;
@@ -195,6 +243,15 @@ public Measurement RunIteration(IterationData data)
195
243
return measurement ;
196
244
}
197
245
246
+ // This is necessary for the CORE runtime to clean up the memory from the clock.
247
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
248
+ private double MeasureAction ( Action < long > action , long arg )
249
+ {
250
+ var clock = Clock . Start ( ) ;
251
+ action ( arg ) ;
252
+ return clock . GetElapsed ( ) . GetNanoseconds ( ) ;
253
+ }
254
+
198
255
private ( GcStats , ThreadingStats , double ) GetExtraStats ( IterationData data )
199
256
{
200
257
// we enable monitoring after main target run, for this single iteration which is executed at the end
@@ -218,8 +275,8 @@ public Measurement RunIteration(IterationData data)
218
275
IterationCleanupAction ( ) ; // we run iteration cleanup after collecting GC stats
219
276
220
277
var totalOperationsCount = data . InvokeCount * OperationsPerInvoke ;
221
- GcStats gcStats = ( finalGcStats - initialGcStats ) . WithTotalOperations ( totalOperationsCount ) ;
222
- ThreadingStats threadingStats = ( finalThreadingStats - initialThreadingStats ) . WithTotalOperations ( data . InvokeCount * OperationsPerInvoke ) ;
278
+ GcStats gcStats = ( finalGcStats - initialGcStats ) . WithTotalOperationsAndSurvivedBytes ( totalOperationsCount , survivedBytes ) ;
279
+ ThreadingStats threadingStats = ( finalThreadingStats - initialThreadingStats ) . WithTotalOperations ( totalOperationsCount ) ;
223
280
224
281
return ( gcStats , threadingStats , exceptionsStats . ExceptionsCount / ( double ) totalOperationsCount ) ;
225
282
}
@@ -253,7 +310,7 @@ private void GcCollect()
253
310
ForceGcCollect ( ) ;
254
311
}
255
312
256
- private static void ForceGcCollect ( )
313
+ internal static void ForceGcCollect ( )
257
314
{
258
315
GC . Collect ( ) ;
259
316
GC . WaitForPendingFinalizers ( ) ;
0 commit comments