@@ -20,6 +20,7 @@ public class Engine : IEngine
20
20
21
21
[ PublicAPI ] public IHost Host { get ; }
22
22
[ PublicAPI ] public Action < long > WorkloadAction { get ; }
23
+ [ PublicAPI ] public Action < long > WorkloadActionNoUnroll { get ; }
23
24
[ PublicAPI ] public Action Dummy1Action { get ; }
24
25
[ PublicAPI ] public Action Dummy2Action { get ; }
25
26
[ PublicAPI ] public Action Dummy3Action { get ; }
@@ -45,19 +46,23 @@ public class Engine : IEngine
45
46
private readonly EnginePilotStage pilotStage ;
46
47
private readonly EngineWarmupStage warmupStage ;
47
48
private readonly EngineActualStage actualStage ;
48
- private readonly bool includeExtraStats ;
49
49
private readonly Random random ;
50
+ private readonly bool includeExtraStats , includeSurvivedMemory ;
51
+
52
+ private long survivedBytes ;
53
+ private bool survivedBytesMeasured ;
54
+ private static Func < long > GetTotalBytes { get ; set ; }
50
55
51
56
internal Engine (
52
57
IHost host ,
53
58
IResolver resolver ,
54
- Action dummy1Action , Action dummy2Action , Action dummy3Action , Action < long > overheadAction , Action < long > workloadAction , Job targetJob ,
59
+ Action dummy1Action , Action dummy2Action , Action dummy3Action , Action < long > overheadAction , Action < long > workloadAction , Action < long > workloadActionNoUnroll , Job targetJob ,
55
60
Action globalSetupAction , Action globalCleanupAction , Action iterationSetupAction , Action iterationCleanupAction , long operationsPerInvoke ,
56
- bool includeExtraStats , string benchmarkName )
61
+ bool includeExtraStats , bool includeSurvivedMemory , string benchmarkName )
57
62
{
58
-
59
63
Host = host ;
60
64
OverheadAction = overheadAction ;
65
+ WorkloadActionNoUnroll = workloadActionNoUnroll ;
61
66
Dummy1Action = dummy1Action ;
62
67
Dummy2Action = dummy2Action ;
63
68
Dummy3Action = dummy3Action ;
@@ -70,6 +75,7 @@ internal Engine(
70
75
OperationsPerInvoke = operationsPerInvoke ;
71
76
this . includeExtraStats = includeExtraStats ;
72
77
BenchmarkName = benchmarkName ;
78
+ this . includeSurvivedMemory = includeSurvivedMemory ;
73
79
74
80
Resolver = resolver ;
75
81
@@ -85,6 +91,55 @@ internal Engine(
85
91
actualStage = new EngineActualStage ( this ) ;
86
92
87
93
random = new Random ( 12345 ) ; // we are using constant seed to try to get repeatable results
94
+
95
+ if ( includeSurvivedMemory && GetTotalBytes is null )
96
+ {
97
+ // CreateGetTotalBytesFunc enables monitoring, so we only call it if we need to measure survived memory.
98
+ GetTotalBytes = CreateGetTotalBytesFunc ( ) ;
99
+
100
+ // Necessary for CORE runtimes.
101
+ // Measure bytes to allow GC monitor to make its allocations.
102
+ GetTotalBytes ( ) ;
103
+ // Run the clock once to allow it to make its allocations.
104
+ MeasureAction ( _ => { } , 0 ) ;
105
+ GetTotalBytes ( ) ;
106
+ }
107
+ }
108
+
109
+ private static Func < long > CreateGetTotalBytesFunc ( )
110
+ {
111
+ // Don't try to measure in Mono, Monitoring is not available, and GC.GetTotalMemory is very inaccurate.
112
+ if ( RuntimeInformation . IsMono )
113
+ return ( ) => 0 ;
114
+ try
115
+ {
116
+ // Docs say this should be available in .NET Core 2.1, but it throws an exception.
117
+ // Just try this on all non-Mono runtimes, fallback to GC.GetTotalMemory.
118
+ AppDomain . MonitoringIsEnabled = true ;
119
+ return ( ) =>
120
+ {
121
+ // Enforce GC.Collect here to make sure we get accurate results.
122
+ ForceGcCollect ( ) ;
123
+ return AppDomain . CurrentDomain . MonitoringSurvivedMemorySize ;
124
+ } ;
125
+ }
126
+ catch
127
+ {
128
+ return ( ) =>
129
+ {
130
+ // Enforce GC.Collect here to make sure we get accurate results.
131
+ ForceGcCollect ( ) ;
132
+ return GC . GetTotalMemory ( true ) ;
133
+ } ;
134
+ }
135
+ }
136
+
137
+ internal Engine WithInitialData ( Engine other )
138
+ {
139
+ // Copy the survived bytes from the other engine so we only measure it once.
140
+ survivedBytes = other . survivedBytes ;
141
+ survivedBytesMeasured = other . survivedBytesMeasured ;
142
+ return this ;
88
143
}
89
144
90
145
public void Dispose ( )
@@ -160,7 +215,9 @@ public Measurement RunIteration(IterationData data)
160
215
var action = isOverhead ? OverheadAction : WorkloadAction ;
161
216
162
217
if ( ! isOverhead )
218
+ {
163
219
IterationSetupAction ( ) ;
220
+ }
164
221
165
222
GcCollect ( ) ;
166
223
@@ -169,10 +226,36 @@ public Measurement RunIteration(IterationData data)
169
226
170
227
Span < byte > stackMemory = randomizeMemory ? stackalloc byte [ random . Next ( 32 ) ] : Span < byte > . Empty ;
171
228
172
- // Measure
173
- var clock = Clock . Start ( ) ;
174
- action ( invokeCount / unrollFactor ) ;
175
- var clockSpan = clock . GetElapsed ( ) ;
229
+ bool needsSurvivedMeasurement = includeSurvivedMemory && ! isOverhead && ! survivedBytesMeasured ;
230
+ double nanoseconds ;
231
+ if ( needsSurvivedMeasurement )
232
+ {
233
+ // Measure survived bytes for only the first invocation.
234
+ survivedBytesMeasured = true ;
235
+ if ( totalOperations == 1 )
236
+ {
237
+ // Measure normal invocation for both survived memory and time.
238
+ long beforeBytes = GetTotalBytes ( ) ;
239
+ nanoseconds = MeasureAction ( action , invokeCount / unrollFactor ) ;
240
+ long afterBytes = GetTotalBytes ( ) ;
241
+ survivedBytes = afterBytes - beforeBytes ;
242
+ }
243
+ else
244
+ {
245
+ // Measure a single invocation for survived memory, plus normal invocations for time.
246
+ ++ totalOperations ;
247
+ long beforeBytes = GetTotalBytes ( ) ;
248
+ nanoseconds = MeasureAction ( WorkloadActionNoUnroll , 1 ) ;
249
+ long afterBytes = GetTotalBytes ( ) ;
250
+ survivedBytes = afterBytes - beforeBytes ;
251
+ nanoseconds += MeasureAction ( action , invokeCount / unrollFactor ) ;
252
+ }
253
+ }
254
+ else
255
+ {
256
+ // Measure time normally.
257
+ nanoseconds = MeasureAction ( action , invokeCount / unrollFactor ) ;
258
+ }
176
259
177
260
if ( EngineEventSource . Log . IsEnabled ( ) )
178
261
EngineEventSource . Log . IterationStop ( data . IterationMode , data . IterationStage , totalOperations ) ;
@@ -186,7 +269,7 @@ public Measurement RunIteration(IterationData data)
186
269
GcCollect ( ) ;
187
270
188
271
// Results
189
- var measurement = new Measurement ( 0 , data . IterationMode , data . IterationStage , data . Index , totalOperations , clockSpan . GetNanoseconds ( ) ) ;
272
+ var measurement = new Measurement ( 0 , data . IterationMode , data . IterationStage , data . Index , totalOperations , nanoseconds ) ;
190
273
WriteLine ( measurement . ToString ( ) ) ;
191
274
if ( measurement . IterationStage == IterationStage . Jitting )
192
275
jittingMeasurements . Add ( measurement ) ;
@@ -196,6 +279,15 @@ public Measurement RunIteration(IterationData data)
196
279
return measurement ;
197
280
}
198
281
282
+ // This is necessary for the CORE runtime to clean up the memory from the clock.
283
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
284
+ private double MeasureAction ( Action < long > action , long arg )
285
+ {
286
+ var clock = Clock . Start ( ) ;
287
+ action ( arg ) ;
288
+ return clock . GetElapsed ( ) . GetNanoseconds ( ) ;
289
+ }
290
+
199
291
private ( GcStats , ThreadingStats , double ) GetExtraStats ( IterationData data )
200
292
{
201
293
// we enable monitoring after main target run, for this single iteration which is executed at the end
@@ -219,7 +311,7 @@ public Measurement RunIteration(IterationData data)
219
311
IterationCleanupAction ( ) ; // we run iteration cleanup after collecting GC stats
220
312
221
313
var totalOperationsCount = data . InvokeCount * OperationsPerInvoke ;
222
- GcStats gcStats = ( finalGcStats - initialGcStats ) . WithTotalOperations ( totalOperationsCount ) ;
314
+ GcStats gcStats = ( finalGcStats - initialGcStats ) . WithTotalOperationsAndSurvivedBytes ( data . InvokeCount * OperationsPerInvoke , survivedBytes ) ;
223
315
ThreadingStats threadingStats = ( finalThreadingStats - initialThreadingStats ) . WithTotalOperations ( data . InvokeCount * OperationsPerInvoke ) ;
224
316
225
317
return ( gcStats , threadingStats , exceptionsStats . ExceptionsCount / ( double ) totalOperationsCount ) ;
0 commit comments