Skip to content

Commit 7647117

Browse files
committed
Capture total memory before and after running workload to calculate survived memory.
Add survived memory value to Measurement, only print it if it's non-zero.
1 parent 539bdb0 commit 7647117

File tree

4 files changed

+81
-34
lines changed

4 files changed

+81
-34
lines changed

src/BenchmarkDotNet/Engines/Engine.cs

+23-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Globalization;
44
using System.Linq;
5+
using System.Runtime.CompilerServices;
56
using BenchmarkDotNet.Characteristics;
67
using BenchmarkDotNet.Jobs;
78
using BenchmarkDotNet.Portability;
@@ -80,6 +81,12 @@ internal Engine(
8081
warmupStage = new EngineWarmupStage(this);
8182
pilotStage = new EnginePilotStage(this);
8283
actualStage = new EngineActualStage(this);
84+
85+
if (includeSurvivedMemory)
86+
{
87+
// Run the clock once to set static memory (necessary for CORE runtimes).
88+
MeasureAction(_ => { }, 0);
89+
}
8390
}
8491

8592
public void Dispose()
@@ -136,8 +143,8 @@ public RunResults Run()
136143
EngineEventSource.Log.BenchmarkStop(BenchmarkName);
137144

138145
var outlierMode = TargetJob.ResolveValue(AccuracyMode.OutlierModeCharacteristic, Resolver);
139-
140-
return new RunResults(idle, main, outlierMode, workGcHasDone.WithSurvivedBytes(includeSurvivedMemory), threadingStats);
146+
147+
return new RunResults(idle, main, outlierMode, workGcHasDone, threadingStats);
141148
}
142149

143150
public Measurement RunIteration(IterationData data)
@@ -158,9 +165,9 @@ public Measurement RunIteration(IterationData data)
158165
EngineEventSource.Log.IterationStart(data.IterationMode, data.IterationStage, totalOperations);
159166

160167
// Measure
161-
var clock = Clock.Start();
162-
action(invokeCount / unrollFactor);
163-
var clockSpan = clock.GetElapsed();
168+
GcStats.StartMeasuringSurvived(includeSurvivedMemory);
169+
double nanoseconds = MeasureAction(action, invokeCount / unrollFactor);
170+
long survivedBytes = GcStats.StopMeasuringSurvived(includeSurvivedMemory);
164171

165172
if (EngineEventSource.Log.IsEnabled())
166173
EngineEventSource.Log.IterationStop(data.IterationMode, data.IterationStage, totalOperations);
@@ -171,12 +178,21 @@ public Measurement RunIteration(IterationData data)
171178
GcCollect();
172179

173180
// Results
174-
var measurement = new Measurement(0, data.IterationMode, data.IterationStage, data.Index, totalOperations, clockSpan.GetNanoseconds());
181+
var measurement = new Measurement(0, data.IterationMode, data.IterationStage, data.Index, totalOperations, nanoseconds, survivedBytes);
175182
WriteLine(measurement.ToString());
176183

177184
return measurement;
178185
}
179186

187+
// This is necessary for the CORE runtime to clean up the memory from the clock.
188+
[MethodImpl(MethodImplOptions.NoInlining)]
189+
private double MeasureAction(Action<long> action, long arg)
190+
{
191+
var clock = Clock.Start();
192+
action(arg);
193+
return clock.GetElapsed().GetNanoseconds();
194+
}
195+
180196
private (GcStats, ThreadingStats) GetExtraStats(IterationData data)
181197
{
182198
// we enable monitoring after main target run, for this single iteration which is executed at the end
@@ -196,7 +212,7 @@ public Measurement RunIteration(IterationData data)
196212

197213
IterationCleanupAction(); // we run iteration cleanup after collecting GC stats
198214

199-
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke);
215+
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperationsAndSurvivedBytes(data.InvokeCount * OperationsPerInvoke);
200216
ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke);
201217

202218
return (gcStats, threadingStats);

src/BenchmarkDotNet/Engines/GcStats.cs

+32-23
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,8 @@ public long BytesAllocatedPerOperation
7777
Math.Max(0, left.SurvivedBytes - right.SurvivedBytes));
7878
}
7979

80-
public GcStats WithTotalOperations(long totalOperationsCount)
81-
=> this + new GcStats(0, 0, 0, 0, totalOperationsCount, 0);
82-
83-
public GcStats WithSurvivedBytes(bool getBytes)
84-
=> this + new GcStats(0, 0, 0, 0, 0, GetTotalBytes(getBytes));
80+
public GcStats WithTotalOperationsAndSurvivedBytes(long totalOperationsCount)
81+
=> this + new GcStats(0, 0, 0, 0, totalOperationsCount, _totalMeasured);
8582

8683
public int GetCollectionsCount(int generation)
8784
{
@@ -141,24 +138,6 @@ public static GcStats ReadFinal()
141138
public static GcStats FromForced(int forcedFullGarbageCollections)
142139
=> new GcStats(forcedFullGarbageCollections, forcedFullGarbageCollections, forcedFullGarbageCollections, 0, 0, 0);
143140

144-
private static long GetTotalBytes(bool actual)
145-
{
146-
if (!actual)
147-
return 0;
148-
149-
if (RuntimeInformation.IsFullFramework) // it can be a .NET app consuming our .NET Standard package
150-
{
151-
AppDomain.MonitoringIsEnabled = true;
152-
153-
// Enforce GC.Collect here just to make sure we get accurate results
154-
GC.Collect();
155-
return AppDomain.CurrentDomain.MonitoringSurvivedMemorySize;
156-
}
157-
158-
GC.Collect();
159-
return GC.GetTotalMemory(true);
160-
}
161-
162141
private static long GetAllocatedBytes()
163142
{
164143
if (RuntimeInformation.IsMono) // Monitoring is not available in Mono, see http://stackoverflow.com/questions/40234948/how-to-get-the-number-of-allocated-bytes-
@@ -272,5 +251,35 @@ public override int GetHashCode()
272251
return hashCode;
273252
}
274253
}
254+
255+
private static long _totalMeasured = 0;
256+
private static long _currentMeasured;
257+
258+
public static void StartMeasuringSurvived(bool measure)
259+
{
260+
_currentMeasured = GetTotalBytes(measure);
261+
}
262+
263+
public static long StopMeasuringSurvived(bool measure)
264+
{
265+
long measured = GetTotalBytes(measure) - _currentMeasured;
266+
_currentMeasured = 0;
267+
_totalMeasured += measured;
268+
return measured;
269+
}
270+
271+
private static long GetTotalBytes(bool actual)
272+
{
273+
if (!actual || RuntimeInformation.IsMono) // Monitoring is not available in Mono.
274+
return 0;
275+
276+
AppDomain.MonitoringIsEnabled = true;
277+
278+
// Enforce GC.Collect here to make sure we get accurate results.
279+
GC.Collect();
280+
GC.WaitForPendingFinalizers();
281+
GC.Collect();
282+
return AppDomain.CurrentDomain.MonitoringSurvivedMemorySize;
283+
}
275284
}
276285
}

src/BenchmarkDotNet/Engines/RunResults.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ public IEnumerable<Measurement> GetMeasurements()
5555
IterationStage.Result,
5656
++resultIndex,
5757
measurement.Operations,
58-
value);
58+
value,
59+
measurement.SurvivedBytes);
5960
}
6061
}
6162

src/BenchmarkDotNet/Reports/Measurement.cs

+24-3
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ public struct Measurement : IComparable<Measurement>
1919

2020
private const string NsSymbol = "ns";
2121
private const string OpSymbol = "op";
22+
private const string SBSymbol = "B";
2223

23-
private static Measurement Error() => new Measurement(-1, IterationMode.Unknown, IterationStage.Unknown, 0, 0, 0);
24+
private static Measurement Error() => new Measurement(-1, IterationMode.Unknown, IterationStage.Unknown, 0, 0, 0, 0);
2425

2526
private static readonly int IterationInfoNameMaxWidth
2627
= Enum.GetNames(typeof(IterationMode)).Max(text => text.Length) + Enum.GetNames(typeof(IterationStage)).Max(text => text.Length);
@@ -43,6 +44,11 @@ private static readonly int IterationInfoNameMaxWidth
4344
/// </summary>
4445
public double Nanoseconds { get; }
4546

47+
/// <summary>
48+
/// Gets the total number of survived bytes from all operations.
49+
/// </summary>
50+
public long SurvivedBytes { get; }
51+
4652
/// <summary>
4753
/// Creates an instance of <see cref="Measurement"/> struct.
4854
/// </summary>
@@ -52,14 +58,16 @@ private static readonly int IterationInfoNameMaxWidth
5258
/// <param name="iterationIndex"></param>
5359
/// <param name="operations">The number of operations performed.</param>
5460
/// <param name="nanoseconds">The total number of nanoseconds it took to perform all operations.</param>
55-
public Measurement(int launchIndex, IterationMode iterationMode, IterationStage iterationStage, int iterationIndex, long operations, double nanoseconds)
61+
/// <param name="survivedBytes">The total number of survived bytes from all operations.</param>
62+
public Measurement(int launchIndex, IterationMode iterationMode, IterationStage iterationStage, int iterationIndex, long operations, double nanoseconds, long survivedBytes)
5663
{
5764
Operations = operations;
5865
Nanoseconds = nanoseconds;
5966
LaunchIndex = launchIndex;
6067
IterationMode = iterationMode;
6168
IterationStage = iterationStage;
6269
IterationIndex = iterationIndex;
70+
SurvivedBytes = survivedBytes;
6371
}
6472

6573
private static IterationMode ParseIterationMode(string name) => Enum.TryParse(name, out IterationMode mode) ? mode : IterationMode.Unknown;
@@ -98,6 +106,15 @@ public override string ToString()
98106
builder.Append(GetAverageTime().ToString(MainCultureInfo).ToAscii());
99107
builder.Append("/op");
100108

109+
if (SurvivedBytes != 0)
110+
{
111+
builder.Append(", ");
112+
builder.Append(SurvivedBytes.ToString(MainCultureInfo));
113+
builder.Append(' ');
114+
builder.Append(SBSymbol);
115+
builder.Append(" Survived");
116+
}
117+
101118
return builder.ToString();
102119
}
103120

@@ -146,6 +163,7 @@ public static Measurement Parse(ILogger logger, string line, int processIndex)
146163
var measurementsInfoSplit = measurementsInfo.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
147164
long op = 1L;
148165
double ns = double.PositiveInfinity;
166+
long survived = 0;
149167
foreach (string item in measurementsInfoSplit)
150168
{
151169
var measurementSplit = item.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
@@ -159,9 +177,12 @@ public static Measurement Parse(ILogger logger, string line, int processIndex)
159177
case OpSymbol:
160178
op = long.Parse(value, MainCultureInfo);
161179
break;
180+
case SBSymbol:
181+
survived = long.Parse(value, MainCultureInfo);
182+
break;
162183
}
163184
}
164-
return new Measurement(processIndex, iterationMode, iterationStage, iterationIndex, op, ns);
185+
return new Measurement(processIndex, iterationMode, iterationStage, iterationIndex, op, ns, survived);
165186
}
166187
catch (Exception)
167188
{

0 commit comments

Comments
 (0)