Skip to content

Commit 02f1381

Browse files
committed
Isolate allocation measurement.
Remove `GC.Collect()` from allocation measurement.
1 parent c864594 commit 02f1381

File tree

2 files changed

+16
-17
lines changed

2 files changed

+16
-17
lines changed

src/BenchmarkDotNet/Engines/Engine.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,6 @@ private ClockSpan Measure(Action<long> action, long invokeCount)
212212
return clock.GetElapsed();
213213
}
214214

215-
// Make sure tier0 jit doesn't cause any unexpected allocations in this method.
216-
[MethodImpl(CodeGenHelper.AggressiveOptimizationOption)]
217215
private (GcStats, ThreadingStats, double) GetExtraStats(IterationData data)
218216
{
219217
// Warm up the GetAllocatedBytes function before starting the actual measurement.
@@ -224,23 +222,33 @@ private ClockSpan Measure(Action<long> action, long invokeCount)
224222
var initialThreadingStats = ThreadingStats.ReadInitial(); // this method might allocate
225223
var exceptionsStats = new ExceptionsStats(); // allocates
226224
exceptionsStats.StartListening(); // this method might allocate
227-
var initialGcStats = GcStats.ReadInitial();
228225

229-
WorkloadAction(data.InvokeCount / data.UnrollFactor);
226+
// GC collect before measuring allocations, as we do not collect during the measurement.
227+
ForceGcCollect();
228+
var gcStats = MeasureWithGc(data.InvokeCount / data.UnrollFactor);
230229

231-
var finalGcStats = GcStats.ReadFinal();
232230
exceptionsStats.Stop(); // this method might (de)allocate
233231
var finalThreadingStats = ThreadingStats.ReadFinal();
234232

235233
IterationCleanupAction(); // we run iteration cleanup after collecting GC stats
236234

237235
var totalOperationsCount = data.InvokeCount * OperationsPerInvoke;
238-
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperations(totalOperationsCount);
236+
gcStats = gcStats.WithTotalOperations(totalOperationsCount);
239237
ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke);
240238

241239
return (gcStats, threadingStats, exceptionsStats.ExceptionsCount / (double)totalOperationsCount);
242240
}
243241

242+
// Isolate the allocation measurement and skip tier0 jit to make sure we don't get any unexpected allocations.
243+
[MethodImpl(MethodImplOptions.NoInlining | CodeGenHelper.AggressiveOptimizationOption)]
244+
private GcStats MeasureWithGc(long invokeCount)
245+
{
246+
var initialGcStats = GcStats.ReadInitial();
247+
WorkloadAction(invokeCount);
248+
var finalGcStats = GcStats.ReadFinal();
249+
return finalGcStats - initialGcStats;
250+
}
251+
244252
private void RandomizeManagedHeapMemory()
245253
{
246254
// invoke global cleanup before global setup
@@ -267,8 +275,6 @@ private void GcCollect()
267275
ForceGcCollect();
268276
}
269277

270-
// Make sure tier0 jit doesn't cause any unexpected allocations in this method.
271-
[MethodImpl(CodeGenHelper.AggressiveOptimizationOption)]
272278
internal static void ForceGcCollect()
273279
{
274280
GC.Collect();

src/BenchmarkDotNet/Engines/GcStats.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ public int GetCollectionsCount(int generation)
111111
[MethodImpl(CodeGenHelper.AggressiveOptimizationOption)]
112112
public static GcStats ReadInitial()
113113
{
114-
// this will force GC.Collect, so we want to do this before collecting collections counts
115114
long? allocatedBytes = GetAllocatedBytes();
116115

117116
return new GcStats(
@@ -130,9 +129,6 @@ public static GcStats ReadFinal()
130129
GC.CollectionCount(0),
131130
GC.CollectionCount(1),
132131
GC.CollectionCount(2),
133-
134-
// this will force GC.Collect, so we want to do this after collecting collections counts
135-
// to exclude this single full forced collection from results
136132
GetAllocatedBytes(),
137133
0);
138134
}
@@ -149,11 +145,8 @@ public static GcStats FromForced(int forcedFullGarbageCollections)
149145
if (RuntimeInformation.IsWasm)
150146
return null;
151147

152-
// "This instance Int64 property returns the number of bytes that have been allocated by a specific
153-
// AppDomain. The number is accurate as of the last garbage collection." - CLR via C#
154-
// so we enforce GC.Collect here just to make sure we get accurate results
155-
Engine.ForceGcCollect();
156-
148+
// Calling GC.Collect() before calling GC.GetTotalAllocatedBytes appears to interfere with the results for some reason,
149+
// so we just call the API without forcing a collection.
157150
#if NET6_0_OR_GREATER
158151
return GC.GetTotalAllocatedBytes(precise: true);
159152
#else

0 commit comments

Comments
 (0)