Skip to content

Commit 6fbfb04

Browse files
committed
Moved survived bytes measurement from GcStats to Engine.
Fixed ImmutableConfig.HasSurvivedMemoryDiagnoser() (For some reason, Contains(MemoryDiagnoser.WithSurvived) was returning true when it shouldn't).
1 parent ab4135f commit 6fbfb04

File tree

3 files changed

+48
-48
lines changed

3 files changed

+48
-48
lines changed

src/BenchmarkDotNet/Configs/ImmutableConfig.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ internal ImmutableConfig(
9494

9595
public bool HasMemoryDiagnoser() => diagnosers.Any(diagnoser => diagnoser is MemoryDiagnoser);
9696

97-
public bool HasSurvivedMemoryDiagnoser() => diagnosers.Contains(MemoryDiagnoser.WithSurvived);
97+
// diagnosers.Contains(MemoryDiagnoser.WithSurvived) for some reason returns true when it shouldn't.
98+
public bool HasSurvivedMemoryDiagnoser() => diagnosers.Any(diagnoser => diagnoser is MemoryDiagnoser md && md.IncludeSurvived);
9899

99100
public bool HasThreadingDiagnoser() => diagnosers.Contains(ThreadingDiagnoser.Default);
100101

src/BenchmarkDotNet/Engines/Engine.cs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,16 @@ public class Engine : IEngine
4545
private readonly EngineActualStage actualStage;
4646
private readonly bool includeExtraStats, includeSurvivedMemory;
4747

48+
private long _totalMeasuredSurvivedBytes;
49+
private Func<long> GetTotalBytes { get; }
50+
4851
internal Engine(
4952
IHost host,
5053
IResolver resolver,
5154
Action dummy1Action, Action dummy2Action, Action dummy3Action, Action<long> overheadAction, Action<long> workloadAction, Job targetJob,
5255
Action globalSetupAction, Action globalCleanupAction, Action iterationSetupAction, Action iterationCleanupAction, long operationsPerInvoke,
5356
bool includeExtraStats, bool includeSurvivedMemory, string benchmarkName)
5457
{
55-
5658
Host = host;
5759
OverheadAction = overheadAction;
5860
Dummy1Action = dummy1Action;
@@ -82,16 +84,46 @@ internal Engine(
8284
pilotStage = new EnginePilotStage(this);
8385
actualStage = new EngineActualStage(this);
8486

87+
GetTotalBytes = GetTotalBytesFunc();
8588
// Necessary for CORE runtimes.
8689
if (includeSurvivedMemory)
8790
{
88-
// Measure survived once to allow jit to make its allocations.
89-
GcStats.StartMeasuringSurvived(includeSurvivedMemory);
90-
// Run the clock once to set static memory.
91+
// Measure bytes to allow GC monitor to make its allocations.
92+
GetTotalBytes();
93+
// Run the clock once to allow it to make its allocations.
9194
MeasureAction(_ => { }, 0);
92-
GcStats.StopMeasuringSurvived(includeSurvivedMemory);
93-
// Clear total measured to not pollute actual measurement.
94-
GcStats.ClearTotalMeasuredSurvived();
95+
GetTotalBytes();
96+
}
97+
}
98+
99+
private Func<long> GetTotalBytesFunc()
100+
{
101+
// Only enable monitoring if memory diagnoser with survived memory is applied.
102+
// Don't try to measure in Mono, Monitoring is not available, and GC.GetTotalMemory is very inaccurate.
103+
if (!includeSurvivedMemory || RuntimeInformation.IsMono)
104+
{
105+
return () => 0;
106+
}
107+
try
108+
{
109+
// Docs say this should be available in .NET Core 2.1, but it throws an exception.
110+
// Just try this on all non-Mono runtimes, fallback to GC.GetTotalMemory.
111+
AppDomain.MonitoringIsEnabled = true;
112+
return () =>
113+
{
114+
// Enforce GC.Collect here to make sure we get accurate results.
115+
ForceGcCollect();
116+
return AppDomain.CurrentDomain.MonitoringSurvivedMemorySize;
117+
};
118+
}
119+
catch
120+
{
121+
return () =>
122+
{
123+
// Enforce GC.Collect here to make sure we get accurate results.
124+
ForceGcCollect();
125+
return GC.GetTotalMemory(true);
126+
};
95127
}
96128
}
97129

@@ -171,9 +203,11 @@ public Measurement RunIteration(IterationData data)
171203
EngineEventSource.Log.IterationStart(data.IterationMode, data.IterationStage, totalOperations);
172204

173205
// Measure
174-
GcStats.StartMeasuringSurvived(includeSurvivedMemory);
206+
long beforeBytes = GetTotalBytes();
175207
double nanoseconds = MeasureAction(action, invokeCount / unrollFactor);
176-
long survivedBytes = GcStats.StopMeasuringSurvived(includeSurvivedMemory);
208+
long afterBytes = GetTotalBytes();
209+
long survivedBytes = afterBytes - beforeBytes;
210+
_totalMeasuredSurvivedBytes += survivedBytes;
177211

178212
if (EngineEventSource.Log.IsEnabled())
179213
EngineEventSource.Log.IterationStop(data.IterationMode, data.IterationStage, totalOperations);
@@ -218,7 +252,7 @@ private double MeasureAction(Action<long> action, long arg)
218252

219253
IterationCleanupAction(); // we run iteration cleanup after collecting GC stats
220254

221-
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperationsAndSurvivedBytes(data.InvokeCount * OperationsPerInvoke);
255+
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperationsAndSurvivedBytes(data.InvokeCount * OperationsPerInvoke, _totalMeasuredSurvivedBytes);
222256
ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke);
223257

224258
return (gcStats, threadingStats);

src/BenchmarkDotNet/Engines/GcStats.cs

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ public long BytesAllocatedPerOperation
7777
Math.Max(0, left.SurvivedBytes - right.SurvivedBytes));
7878
}
7979

80-
public GcStats WithTotalOperationsAndSurvivedBytes(long totalOperationsCount)
81-
=> this + new GcStats(0, 0, 0, 0, totalOperationsCount, _totalMeasured);
80+
public GcStats WithTotalOperationsAndSurvivedBytes(long totalOperationsCount, long survivedBytes)
81+
=> this + new GcStats(0, 0, 0, 0, totalOperationsCount, survivedBytes);
8282

8383
public int GetCollectionsCount(int generation)
8484
{
@@ -252,40 +252,5 @@ public override int GetHashCode()
252252
return hashCode;
253253
}
254254
}
255-
256-
private static long _totalMeasured = 0;
257-
private static long _currentMeasured;
258-
259-
public static void StartMeasuringSurvived(bool measure)
260-
{
261-
_currentMeasured = GetTotalBytes(measure);
262-
}
263-
264-
public static long StopMeasuringSurvived(bool measure)
265-
{
266-
long measured = GetTotalBytes(measure) - _currentMeasured;
267-
_currentMeasured = 0;
268-
_totalMeasured += measured;
269-
return measured;
270-
}
271-
272-
public static void ClearTotalMeasuredSurvived()
273-
{
274-
_totalMeasured = 0;
275-
}
276-
277-
private static long GetTotalBytes(bool actual)
278-
{
279-
if (!actual || RuntimeInformation.IsMono) // Monitoring is not available in Mono.
280-
return 0;
281-
282-
AppDomain.MonitoringIsEnabled = true;
283-
284-
// Enforce GC.Collect here to make sure we get accurate results.
285-
GC.Collect();
286-
GC.WaitForPendingFinalizers();
287-
GC.Collect();
288-
return AppDomain.CurrentDomain.MonitoringSurvivedMemorySize;
289-
}
290255
}
291256
}

0 commit comments

Comments
 (0)