@@ -253,44 +253,68 @@ public void AllocationQuantumIsNotAnIssueForNetCore21Plus(IToolchain toolchain)
253253
254254 public class MultiThreadedAllocation
255255 {
256- public const int Size = 1_000_000 ;
256+ public const int Size = 1024 ;
257257 public const int ThreadsCount = 10 ;
258258
259+ // We cache the threads in GlobalSetup and reuse them for each benchmark invocation
260+ // to avoid measuring the cost of thread start and join, which varies across different runtimes.
259261 private Thread [ ] threads ;
262+ private volatile bool keepRunning = true ;
263+ private readonly Barrier barrier = new ( ThreadsCount + 1 ) ;
264+ private readonly CountdownEvent countdownEvent = new ( ThreadsCount ) ;
260265
261- [ IterationSetup ]
262- public void SetupIteration ( )
266+ [ GlobalSetup ]
267+ public void Setup ( )
263268 {
264269 threads = Enumerable . Range ( 0 , ThreadsCount )
265- . Select ( _ => new Thread ( ( ) => GC . KeepAlive ( new byte [ Size ] ) ) )
270+ . Select ( _ => new Thread ( ( ) =>
271+ {
272+ while ( keepRunning )
273+ {
274+ barrier . SignalAndWait ( ) ;
275+ GC . KeepAlive ( new byte [ Size ] ) ;
276+ countdownEvent . Signal ( ) ;
277+ }
278+ } ) )
266279 . ToArray ( ) ;
280+ foreach ( var thread in threads )
281+ {
282+ thread . Start ( ) ;
283+ }
267284 }
268285
269- [ Benchmark ]
270- public void Allocate ( )
286+ [ GlobalCleanup ]
287+ public void Cleanup ( )
271288 {
289+ keepRunning = false ;
290+ barrier . SignalAndWait ( ) ;
272291 foreach ( var thread in threads )
273292 {
274- thread . Start ( ) ;
275293 thread . Join ( ) ;
276294 }
277295 }
296+
297+ [ Benchmark ]
298+ public void Allocate ( )
299+ {
300+ countdownEvent . Reset ( ThreadsCount ) ;
301+ barrier . SignalAndWait ( ) ;
302+ countdownEvent . Wait ( ) ;
303+ }
278304 }
279305
280- [ Theory ( Skip = "Test is currently failing on all toolchains." ) ]
306+ [ TheoryEnvSpecific ( "Full Framework cannot measure precisely enough" , EnvRequirement . DotNetCoreOnly ) ]
281307 [ MemberData ( nameof ( GetToolchains ) ) ]
282308 [ Trait ( Constants . Category , Constants . BackwardCompatibilityCategory ) ]
283309 public void MemoryDiagnoserIsAccurateForMultiThreadedBenchmarks ( IToolchain toolchain )
284310 {
285311 long objectAllocationOverhead = IntPtr . Size * 2 ; // pointer to method table + object header word
286312 long arraySizeOverhead = IntPtr . Size ; // array length
287313 long memoryAllocatedPerArray = ( MultiThreadedAllocation . Size + objectAllocationOverhead + arraySizeOverhead ) ;
288- long threadStartAndJoinOverhead = 112 ; // this is more or less a magic number taken from memory profiler
289- long allocatedMemoryPerThread = memoryAllocatedPerArray + threadStartAndJoinOverhead ;
290314
291315 AssertAllocations ( toolchain , typeof ( MultiThreadedAllocation ) , new Dictionary < string , long >
292316 {
293- { nameof ( MultiThreadedAllocation . Allocate ) , allocatedMemoryPerThread * MultiThreadedAllocation . ThreadsCount }
317+ { nameof ( MultiThreadedAllocation . Allocate ) , memoryAllocatedPerArray * MultiThreadedAllocation . ThreadsCount }
294318 } ) ;
295319 }
296320
0 commit comments