9
9
using BenchmarkDotNet . Columns ;
10
10
using BenchmarkDotNet . Configs ;
11
11
using BenchmarkDotNet . Diagnosers ;
12
+ using BenchmarkDotNet . Engines ;
12
13
using BenchmarkDotNet . Extensions ;
13
14
using BenchmarkDotNet . IntegrationTests . Xunit ;
14
15
using BenchmarkDotNet . Jobs ;
19
20
using BenchmarkDotNet . Tests . XUnit ;
20
21
using BenchmarkDotNet . Toolchains ;
21
22
using BenchmarkDotNet . Toolchains . CoreRt ;
23
+ using BenchmarkDotNet . Toolchains . CsProj ;
22
24
using BenchmarkDotNet . Toolchains . InProcess . Emit ;
23
25
using Xunit ;
24
26
using Xunit . Abstractions ;
@@ -69,6 +71,64 @@ public void MemoryDiagnoserIsAccurate(IToolchain toolchain)
69
71
} ) ;
70
72
}
71
73
74
+ public class AccurateSurvived
75
+ {
76
+ [ Benchmark ] public byte [ ] EightBytesArray ( ) => new byte [ 8 ] ;
77
+ [ Benchmark ] public byte [ ] SixtyFourBytesArray ( ) => new byte [ 64 ] ;
78
+ [ Benchmark ] public Task < int > AllocateTask ( ) => Task . FromResult ( default ( int ) ) ;
79
+
80
+
81
+ public byte [ ] bytes8 ;
82
+ public byte [ ] bytes64 ;
83
+ public Task < int > task ;
84
+
85
+ [ GlobalSetup ( Targets = new string [ ] { nameof ( EightBytesArrayNoAllocate ) , nameof ( SixtyFourBytesArrayNoAllocate ) } ) ]
86
+ public void SetupNoAllocate ( )
87
+ {
88
+ bytes8 = new byte [ 8 ] ;
89
+ bytes64 = new byte [ 64 ] ;
90
+ }
91
+
92
+ [ Benchmark ] public byte [ ] EightBytesArrayNoAllocate ( ) => bytes8 ;
93
+ [ Benchmark ] public byte [ ] SixtyFourBytesArrayNoAllocate ( ) => bytes64 ;
94
+
95
+
96
+ [ Benchmark ] public void EightBytesArraySurvive ( ) => bytes8 = new byte [ 8 ] ;
97
+ [ Benchmark ] public void SixtyFourBytesArraySurvive ( ) => bytes64 = new byte [ 64 ] ;
98
+ [ Benchmark ] public void AllocateTaskSurvive ( ) => task = Task . FromResult ( default ( int ) ) ;
99
+
100
+
101
+ [ Benchmark ] public void EightBytesArrayAllocateNoSurvive ( ) => DeadCodeEliminationHelper . KeepAliveWithoutBoxing ( new byte [ 8 ] ) ;
102
+ [ Benchmark ] public void SixtyFourBytesArrayAllocateNoSurvive ( ) => DeadCodeEliminationHelper . KeepAliveWithoutBoxing ( new byte [ 64 ] ) ;
103
+ [ Benchmark ] public void TaskAllocateNoSurvive ( ) => DeadCodeEliminationHelper . KeepAliveWithoutBoxing ( Task . FromResult ( default ( int ) ) ) ;
104
+ }
105
+
106
+ [ Theory , MemberData ( nameof ( GetToolchains ) ) ]
107
+ [ Trait ( Constants . Category , Constants . BackwardCompatibilityCategory ) ]
108
+ public void MemoryDiagnoserSurvivedIsAccurate ( IToolchain toolchain )
109
+ {
110
+ long objectAllocationOverhead = IntPtr . Size * 2 ; // pointer to method table + object header word
111
+ long arraySizeOverhead = IntPtr . Size ; // array length
112
+
113
+ AssertSurvived ( toolchain , typeof ( AccurateSurvived ) , new Dictionary < string , long >
114
+ {
115
+ { nameof ( AccurateSurvived . EightBytesArray ) , 0 } ,
116
+ { nameof ( AccurateSurvived . SixtyFourBytesArray ) , 0 } ,
117
+ { nameof ( AccurateSurvived . AllocateTask ) , 0 } ,
118
+
119
+ { nameof ( AccurateSurvived . EightBytesArrayNoAllocate ) , 0 } ,
120
+ { nameof ( AccurateSurvived . SixtyFourBytesArrayNoAllocate ) , 0 } ,
121
+
122
+ { nameof ( AccurateSurvived . EightBytesArraySurvive ) , 8 + objectAllocationOverhead + arraySizeOverhead } ,
123
+ { nameof ( AccurateSurvived . SixtyFourBytesArraySurvive ) , 64 + objectAllocationOverhead + arraySizeOverhead } ,
124
+ { nameof ( AccurateSurvived . AllocateTaskSurvive ) , CalculateRequiredSpace < Task < int > > ( ) } ,
125
+
126
+ { nameof ( AccurateSurvived . EightBytesArrayAllocateNoSurvive ) , 0 } ,
127
+ { nameof ( AccurateSurvived . SixtyFourBytesArrayAllocateNoSurvive ) , 0 } ,
128
+ { nameof ( AccurateSurvived . TaskAllocateNoSurvive ) , 0 } ,
129
+ } ) ;
130
+ }
131
+
72
132
public class AllocatingGlobalSetupAndCleanup
73
133
{
74
134
private List < int > list ;
@@ -102,6 +162,16 @@ public void MemoryDiagnoserDoesNotIncludeAllocationsFromSetupAndCleanup(IToolcha
102
162
} ) ;
103
163
}
104
164
165
+ [ Theory , MemberData ( nameof ( GetToolchains ) ) ]
166
+ [ Trait ( Constants . Category , Constants . BackwardCompatibilityCategory ) ]
167
+ public void MemoryDiagnoserDoesNotIncludeSurvivedFromSetupAndCleanup ( IToolchain toolchain )
168
+ {
169
+ AssertSurvived ( toolchain , typeof ( AllocatingGlobalSetupAndCleanup ) , new Dictionary < string , long >
170
+ {
171
+ { nameof ( AllocatingGlobalSetupAndCleanup . AllocateNothing ) , 0 }
172
+ } ) ;
173
+ }
174
+
105
175
public class NoAllocationsAtAll
106
176
{
107
177
[ Benchmark ] public void EmptyMethod ( ) { }
@@ -117,6 +187,16 @@ public void EngineShouldNotInterfereAllocationResults(IToolchain toolchain)
117
187
} ) ;
118
188
}
119
189
190
+ [ Theory , MemberData ( nameof ( GetToolchains ) ) ]
191
+ [ Trait ( Constants . Category , Constants . BackwardCompatibilityCategory ) ]
192
+ public void EngineShouldNotInterfereSurvivedResults ( IToolchain toolchain )
193
+ {
194
+ AssertSurvived ( toolchain , typeof ( NoAllocationsAtAll ) , new Dictionary < string , long >
195
+ {
196
+ { nameof ( NoAllocationsAtAll . EmptyMethod ) , 0 }
197
+ } ) ;
198
+ }
199
+
120
200
public class NoBoxing
121
201
{
122
202
[ Benchmark ] public ValueTuple < int > ReturnsValueType ( ) => new ValueTuple < int > ( 0 ) ;
@@ -132,10 +212,29 @@ public void EngineShouldNotIntroduceBoxing(IToolchain toolchain)
132
212
} ) ;
133
213
}
134
214
215
+ [ Theory , MemberData ( nameof ( GetToolchains ) ) ]
216
+ [ Trait ( Constants . Category , Constants . BackwardCompatibilityCategory ) ]
217
+ public void EngineShouldNotIntroduceBoxingSurvived ( IToolchain toolchain )
218
+ {
219
+ AssertSurvived ( toolchain , typeof ( NoBoxing ) , new Dictionary < string , long >
220
+ {
221
+ { nameof ( NoBoxing . ReturnsValueType ) , 0 }
222
+ } ) ;
223
+ }
224
+
135
225
public class NonAllocatingAsynchronousBenchmarks
136
226
{
137
227
private readonly Task < int > completedTaskOfT = Task . FromResult ( default ( int ) ) ; // we store it in the field, because Task<T> is reference type so creating it allocates heap memory
138
228
229
+ [ GlobalSetup ]
230
+ public void Setup ( )
231
+ {
232
+ // Run once to set static memory.
233
+ DeadCodeEliminationHelper . KeepAliveWithoutBoxing ( CompletedTask ( ) ) ;
234
+ DeadCodeEliminationHelper . KeepAliveWithoutBoxing ( CompletedTaskOfT ( ) ) ;
235
+ DeadCodeEliminationHelper . KeepAliveWithoutBoxing ( CompletedValueTaskOfT ( ) ) ;
236
+ }
237
+
139
238
[ Benchmark ] public Task CompletedTask ( ) => Task . CompletedTask ;
140
239
141
240
[ Benchmark ] public Task < int > CompletedTaskOfT ( ) => completedTaskOfT ;
@@ -155,6 +254,18 @@ public void AwaitingTasksShouldNotInterfereAllocationResults(IToolchain toolchai
155
254
} ) ;
156
255
}
157
256
257
+ [ Theory , MemberData ( nameof ( GetToolchains ) ) ]
258
+ [ Trait ( Constants . Category , Constants . BackwardCompatibilityCategory ) ]
259
+ public void AwaitingTasksShouldNotInterfereSurvivedResults ( IToolchain toolchain )
260
+ {
261
+ AssertSurvived ( toolchain , typeof ( NonAllocatingAsynchronousBenchmarks ) , new Dictionary < string , long >
262
+ {
263
+ { nameof ( NonAllocatingAsynchronousBenchmarks . CompletedTask ) , 0 } ,
264
+ { nameof ( NonAllocatingAsynchronousBenchmarks . CompletedTaskOfT ) , 0 } ,
265
+ { nameof ( NonAllocatingAsynchronousBenchmarks . CompletedValueTaskOfT ) , 0 }
266
+ } ) ;
267
+ }
268
+
158
269
public class WithOperationsPerInvokeBenchmarks
159
270
{
160
271
[ Benchmark ( OperationsPerInvoke = 4 ) ]
@@ -257,7 +368,7 @@ public void MemoryDiagnoserIsAccurateForMultiThreadedBenchmarks(IToolchain toolc
257
368
258
369
private void AssertAllocations ( IToolchain toolchain , Type benchmarkType , Dictionary < string , long > benchmarksAllocationsValidators )
259
370
{
260
- var config = CreateConfig ( toolchain ) ;
371
+ var config = CreateConfig ( toolchain , MemoryDiagnoser . Default ) ;
261
372
var benchmarks = BenchmarkConverter . TypeToBenchmarks ( benchmarkType , config ) ;
262
373
263
374
var summary = BenchmarkRunner . Run ( benchmarks ) ;
@@ -285,7 +396,37 @@ private void AssertAllocations(IToolchain toolchain, Type benchmarkType, Diction
285
396
}
286
397
}
287
398
288
- private IConfig CreateConfig ( IToolchain toolchain )
399
+ private void AssertSurvived ( IToolchain toolchain , Type benchmarkType , Dictionary < string , long > benchmarkSurvivedValidators )
400
+ {
401
+ // Core has survived memory measurement problems.
402
+ // See https://github.com/dotnet/runtime/issues/45446
403
+ if ( toolchain is CsProjCoreToolchain || toolchain is CoreRtToolchain ) // CoreRt actually does measure accurately in a normal benchmark run, but doesn't with the specific version used in these tests.
404
+ return ;
405
+
406
+ var config = CreateConfig ( toolchain , MemoryDiagnoser . WithSurvived ) ;
407
+ var benchmarks = BenchmarkConverter . TypeToBenchmarks ( benchmarkType , config ) ;
408
+
409
+ var summary = BenchmarkRunner . Run ( benchmarks ) ;
410
+
411
+ foreach ( var benchmarkSurvivedValidator in benchmarkSurvivedValidators )
412
+ {
413
+ // CoreRT is missing some of the CoreCLR threading/task related perf improvements, so sizeof(Task<int>) calculated for CoreCLR < sizeof(Task<int>) on CoreRT
414
+ // see https://github.com/dotnet/corert/issues/5705 for more
415
+ if ( benchmarkSurvivedValidator . Key == nameof ( AccurateSurvived . AllocateTaskSurvive ) && toolchain is CoreRtToolchain )
416
+ continue ;
417
+
418
+ var survivedBenchmarks = benchmarks . BenchmarksCases . Where ( benchmark => benchmark . Descriptor . WorkloadMethodDisplayInfo == benchmarkSurvivedValidator . Key ) . ToArray ( ) ;
419
+
420
+ foreach ( var benchmark in survivedBenchmarks )
421
+ {
422
+ var benchmarkReport = summary . Reports . Single ( report => report . BenchmarkCase == benchmark ) ;
423
+
424
+ Assert . Equal ( benchmarkSurvivedValidator . Value , benchmarkReport . GcStats . SurvivedBytes ) ;
425
+ }
426
+ }
427
+ }
428
+
429
+ private IConfig CreateConfig ( IToolchain toolchain , MemoryDiagnoser memoryDiagnoser )
289
430
=> ManualConfig . CreateEmpty ( )
290
431
. AddJob ( Job . ShortRun
291
432
. WithEvaluateOverhead ( false ) // no need to run idle for this test
@@ -294,7 +435,7 @@ private IConfig CreateConfig(IToolchain toolchain)
294
435
. WithGcForce ( false )
295
436
. WithToolchain ( toolchain ) )
296
437
. AddColumnProvider ( DefaultColumnProviders . Instance )
297
- . AddDiagnoser ( MemoryDiagnoser . Default )
438
+ . AddDiagnoser ( memoryDiagnoser )
298
439
. AddLogger ( toolchain . IsInProcess ? ConsoleLogger . Default : new OutputLogger ( output ) ) ; // we can't use OutputLogger for the InProcess toolchains because it allocates memory on the same thread
299
440
300
441
// note: don't copy, never use in production systems (it should work but I am not 100% sure)
0 commit comments