diff --git a/src/BenchmarkDotNet/Engines/Engine.cs b/src/BenchmarkDotNet/Engines/Engine.cs index 572f3c4c3f..21d2040185 100644 --- a/src/BenchmarkDotNet/Engines/Engine.cs +++ b/src/BenchmarkDotNet/Engines/Engine.cs @@ -212,8 +212,6 @@ private ClockSpan Measure(Action action, long invokeCount) return clock.GetElapsed(); } - // Make sure tier0 jit doesn't cause any unexpected allocations in this method. - [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] private (GcStats, ThreadingStats, double) GetExtraStats(IterationData data) { // Warm up the GetAllocatedBytes function before starting the actual measurement. @@ -224,23 +222,33 @@ private ClockSpan Measure(Action action, long invokeCount) var initialThreadingStats = ThreadingStats.ReadInitial(); // this method might allocate var exceptionsStats = new ExceptionsStats(); // allocates exceptionsStats.StartListening(); // this method might allocate - var initialGcStats = GcStats.ReadInitial(); - WorkloadAction(data.InvokeCount / data.UnrollFactor); + // GC collect before measuring allocations, as we do not collect during the measurement. + ForceGcCollect(); + var gcStats = MeasureWithGc(data.InvokeCount / data.UnrollFactor); - var finalGcStats = GcStats.ReadFinal(); exceptionsStats.Stop(); // this method might (de)allocate var finalThreadingStats = ThreadingStats.ReadFinal(); IterationCleanupAction(); // we run iteration cleanup after collecting GC stats var totalOperationsCount = data.InvokeCount * OperationsPerInvoke; - GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperations(totalOperationsCount); + gcStats = gcStats.WithTotalOperations(totalOperationsCount); ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke); return (gcStats, threadingStats, exceptionsStats.ExceptionsCount / (double)totalOperationsCount); } + // Isolate the allocation measurement and skip tier0 jit to make sure we don't get any unexpected allocations. + [MethodImpl(MethodImplOptions.NoInlining | CodeGenHelper.AggressiveOptimizationOption)] + private GcStats MeasureWithGc(long invokeCount) + { + var initialGcStats = GcStats.ReadInitial(); + WorkloadAction(invokeCount); + var finalGcStats = GcStats.ReadFinal(); + return finalGcStats - initialGcStats; + } + private void RandomizeManagedHeapMemory() { // invoke global cleanup before global setup @@ -267,8 +275,6 @@ private void GcCollect() ForceGcCollect(); } - // Make sure tier0 jit doesn't cause any unexpected allocations in this method. - [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] internal static void ForceGcCollect() { GC.Collect(); diff --git a/src/BenchmarkDotNet/Engines/GcStats.cs b/src/BenchmarkDotNet/Engines/GcStats.cs index f5aae44bdf..a7ea06e1fa 100644 --- a/src/BenchmarkDotNet/Engines/GcStats.cs +++ b/src/BenchmarkDotNet/Engines/GcStats.cs @@ -111,7 +111,6 @@ public int GetCollectionsCount(int generation) [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)] public static GcStats ReadInitial() { - // this will force GC.Collect, so we want to do this before collecting collections counts long? allocatedBytes = GetAllocatedBytes(); return new GcStats( @@ -130,9 +129,6 @@ public static GcStats ReadFinal() GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2), - - // this will force GC.Collect, so we want to do this after collecting collections counts - // to exclude this single full forced collection from results GetAllocatedBytes(), 0); } @@ -149,11 +145,8 @@ public static GcStats FromForced(int forcedFullGarbageCollections) if (RuntimeInformation.IsWasm) return null; - // "This instance Int64 property returns the number of bytes that have been allocated by a specific - // AppDomain. The number is accurate as of the last garbage collection." - CLR via C# - // so we enforce GC.Collect here just to make sure we get accurate results - Engine.ForceGcCollect(); - + // Calling GC.Collect() before calling GC.GetTotalAllocatedBytes appears to interfere with the results for some reason, + // so we just call the API without forcing a collection. #if NET6_0_OR_GREATER return GC.GetTotalAllocatedBytes(precise: true); #else