Skip to content

Commit 4104143

Browse files
committed
Added --memorySurvived command line option.
Added WorkloadActionSingleInvoke. Only measure survived memory for the first WorkloadAction, using the SingleInvoke, instead of every iteration.
1 parent 6fbfb04 commit 4104143

File tree

7 files changed

+78
-20
lines changed

7 files changed

+78
-20
lines changed

src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public class CommandLineOptions
4747
[Option('a', "artifacts", Required = false, HelpText = "Valid path to accessible directory")]
4848
public DirectoryInfo ArtifactsDirectory { get; set; }
4949

50+
[Option("memorySurvived", Required = false, Default = false, HelpText = "Measures survived memory.")]
51+
public bool UseSurvivedMemoryDiagnoser { get; set; }
52+
5053
[Option("outliers", Required = false, Default = OutlierMode.RemoveUpper, HelpText = "DontRemove/RemoveUpper/RemoveLower/RemoveAll")]
5154
public OutlierMode Outliers { get; set; }
5255

src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,11 @@ private static IConfig CreateConfig(CommandLineOptions options, IConfig globalCo
193193
.Select(counterName => (HardwareCounter)Enum.Parse(typeof(HardwareCounter), counterName, ignoreCase: true))
194194
.ToArray());
195195

196-
if (options.UseMemoryDiagnoser)
196+
if (options.UseSurvivedMemoryDiagnoser)
197+
config.AddDiagnoser(MemoryDiagnoser.WithSurvived);
198+
else if (options.UseMemoryDiagnoser)
197199
config.AddDiagnoser(MemoryDiagnoser.Default);
200+
198201
if (options.UseThreadingDiagnoser)
199202
config.AddDiagnoser(ThreadingDiagnoser.Default);
200203
if (options.UseDisassemblyDiagnoser)

src/BenchmarkDotNet/Diagnosers/MemoryDiagnoser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ private class SurvivedMemoryMetricDescriptor : IMetricDescriptor
6868

6969
public string Id => "Survived Memory";
7070
public string DisplayName => "Survived";
71-
public string Legend => "Total memory survived after all operations (managed only, inclusive, 1KB = 1024B)";
71+
public string Legend => "Memory survived after the first operation (managed only, inclusive, 1KB = 1024B)";
7272
public string NumberFormat => "N0";
7373
public UnitType UnitType => UnitType.Size;
7474
public string Unit => SizeUnit.B.Name;

src/BenchmarkDotNet/Engines/Engine.cs

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class Engine : IEngine
1919

2020
[PublicAPI] public IHost Host { get; }
2121
[PublicAPI] public Action<long> WorkloadAction { get; }
22+
[PublicAPI] public Action WorkloadActionSingleInvoke { get; }
2223
[PublicAPI] public Action Dummy1Action { get; }
2324
[PublicAPI] public Action Dummy2Action { get; }
2425
[PublicAPI] public Action Dummy3Action { get; }
@@ -45,18 +46,21 @@ public class Engine : IEngine
4546
private readonly EngineActualStage actualStage;
4647
private readonly bool includeExtraStats, includeSurvivedMemory;
4748

48-
private long _totalMeasuredSurvivedBytes;
49+
// These must be static since more than one Engine is used.
50+
private static long survivedBytes;
51+
private static bool survivedBytesMeasured;
4952
private Func<long> GetTotalBytes { get; }
5053

5154
internal Engine(
5255
IHost host,
5356
IResolver resolver,
54-
Action dummy1Action, Action dummy2Action, Action dummy3Action, Action<long> overheadAction, Action<long> workloadAction, Job targetJob,
57+
Action dummy1Action, Action dummy2Action, Action dummy3Action, Action<long> overheadAction, Action<long> workloadAction, Action workloadActionSingleInvoke, Job targetJob,
5558
Action globalSetupAction, Action globalCleanupAction, Action iterationSetupAction, Action iterationCleanupAction, long operationsPerInvoke,
5659
bool includeExtraStats, bool includeSurvivedMemory, string benchmarkName)
5760
{
5861
Host = host;
5962
OverheadAction = overheadAction;
63+
WorkloadActionSingleInvoke = workloadActionSingleInvoke;
6064
Dummy1Action = dummy1Action;
6165
Dummy2Action = dummy2Action;
6266
Dummy3Action = dummy3Action;
@@ -84,14 +88,15 @@ internal Engine(
8488
pilotStage = new EnginePilotStage(this);
8589
actualStage = new EngineActualStage(this);
8690

87-
GetTotalBytes = GetTotalBytesFunc();
88-
// Necessary for CORE runtimes.
89-
if (includeSurvivedMemory)
91+
if (includeSurvivedMemory && !survivedBytesMeasured)
9092
{
93+
GetTotalBytes = GetTotalBytesFunc();
94+
95+
// Necessary for CORE runtimes.
9196
// Measure bytes to allow GC monitor to make its allocations.
9297
GetTotalBytes();
9398
// Run the clock once to allow it to make its allocations.
94-
MeasureAction(_ => { }, 0);
99+
MeasureAction(() => { });
95100
GetTotalBytes();
96101
}
97102
}
@@ -100,10 +105,8 @@ private Func<long> GetTotalBytesFunc()
100105
{
101106
// Only enable monitoring if memory diagnoser with survived memory is applied.
102107
// Don't try to measure in Mono, Monitoring is not available, and GC.GetTotalMemory is very inaccurate.
103-
if (!includeSurvivedMemory || RuntimeInformation.IsMono)
104-
{
108+
if (RuntimeInformation.IsMono)
105109
return () => 0;
106-
}
107110
try
108111
{
109112
// Docs say this should be available in .NET Core 2.1, but it throws an exception.
@@ -194,20 +197,32 @@ public Measurement RunIteration(IterationData data)
194197
bool isOverhead = data.IterationMode == IterationMode.Overhead;
195198
var action = isOverhead ? OverheadAction : WorkloadAction;
196199

200+
double nanoseconds = 0;
197201
if (!isOverhead)
202+
{
198203
IterationSetupAction();
199204

205+
if (includeSurvivedMemory && !survivedBytesMeasured)
206+
{
207+
// Measure survived bytes for only the first invocation.
208+
survivedBytesMeasured = true;
209+
++totalOperations;
210+
long beforeBytes = GetTotalBytes();
211+
nanoseconds = MeasureAction(WorkloadActionSingleInvoke);
212+
long afterBytes = GetTotalBytes();
213+
survivedBytes = afterBytes - beforeBytes;
214+
}
215+
}
216+
200217
GcCollect();
201218

202219
if (EngineEventSource.Log.IsEnabled())
203220
EngineEventSource.Log.IterationStart(data.IterationMode, data.IterationStage, totalOperations);
204221

205222
// Measure
206-
long beforeBytes = GetTotalBytes();
207-
double nanoseconds = MeasureAction(action, invokeCount / unrollFactor);
208-
long afterBytes = GetTotalBytes();
209-
long survivedBytes = afterBytes - beforeBytes;
210-
_totalMeasuredSurvivedBytes += survivedBytes;
223+
var clock = Clock.Start();
224+
action(invokeCount / unrollFactor);
225+
nanoseconds += clock.GetElapsed().GetNanoseconds();
211226

212227
if (EngineEventSource.Log.IsEnabled())
213228
EngineEventSource.Log.IterationStop(data.IterationMode, data.IterationStage, totalOperations);
@@ -218,18 +233,18 @@ public Measurement RunIteration(IterationData data)
218233
GcCollect();
219234

220235
// Results
221-
var measurement = new Measurement(0, data.IterationMode, data.IterationStage, data.Index, totalOperations, nanoseconds, survivedBytes);
236+
var measurement = new Measurement(0, data.IterationMode, data.IterationStage, data.Index, totalOperations, nanoseconds);
222237
WriteLine(measurement.ToString());
223238

224239
return measurement;
225240
}
226241

227242
// This is necessary for the CORE runtime to clean up the memory from the clock.
228243
[MethodImpl(MethodImplOptions.NoInlining)]
229-
private double MeasureAction(Action<long> action, long arg)
244+
private double MeasureAction(Action action)
230245
{
231246
var clock = Clock.Start();
232-
action(arg);
247+
action();
233248
return clock.GetElapsed().GetNanoseconds();
234249
}
235250

@@ -252,7 +267,7 @@ private double MeasureAction(Action<long> action, long arg)
252267

253268
IterationCleanupAction(); // we run iteration cleanup after collecting GC stats
254269

255-
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperationsAndSurvivedBytes(data.InvokeCount * OperationsPerInvoke, _totalMeasuredSurvivedBytes);
270+
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperationsAndSurvivedBytes(data.InvokeCount * OperationsPerInvoke, survivedBytes);
256271
ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke);
257272

258273
return (gcStats, threadingStats);

src/BenchmarkDotNet/Engines/EngineFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ private static Engine CreateEngine(EngineParameters engineParameters, Job job, A
112112
engineParameters.Dummy3Action,
113113
idle,
114114
main,
115+
engineParameters.WorkloadActionSingleInvoke,
115116
job,
116117
engineParameters.GlobalSetupAction,
117118
engineParameters.GlobalCleanupAction,

src/BenchmarkDotNet/Engines/EngineParameters.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class EngineParameters
1414
public IHost Host { get; set; }
1515
public Action<long> WorkloadActionNoUnroll { get; set; }
1616
public Action<long> WorkloadActionUnroll { get; set; }
17+
public Action WorkloadActionSingleInvoke { get; set; }
1718
public Action Dummy1Action { get; set; }
1819
public Action Dummy2Action { get; set; }
1920
public Action Dummy3Action { get; set; }

src/BenchmarkDotNet/Templates/BenchmarkType.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
Host = host,
2727
WorkloadActionUnroll = instance.WorkloadActionUnroll,
2828
WorkloadActionNoUnroll = instance.WorkloadActionNoUnroll,
29+
WorkloadActionSingleInvoke = instance.WorkloadActionSingleInvoke,
2930
Dummy1Action = instance.Dummy1,
3031
Dummy2Action = instance.Dummy2,
3132
Dummy3Action = instance.Dummy3,
@@ -149,6 +150,12 @@
149150
consumer.Consume(workloadDelegate($PassArguments$)$ConsumeField$);
150151
}
151152
}
153+
154+
private void WorkloadActionSingleInvoke()
155+
{
156+
$LoadArguments$
157+
consumer.Consume(workloadDelegate($PassArguments$)$ConsumeField$);
158+
}
152159

153160
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
154161
public $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$()
@@ -208,6 +215,12 @@
208215
NonGenericKeepAliveWithoutBoxing(result);
209216
}
210217

218+
private void WorkloadActionSingleInvoke()
219+
{
220+
$LoadArguments$
221+
NonGenericKeepAliveWithoutBoxing(workloadDelegate($PassArguments$));
222+
}
223+
211224
// we must not simply use DeadCodeEliminationHelper.KeepAliveWithoutBoxing<T> because it's generic method
212225
// and stack-only types like Span<T> can not be generic type arguments http://adamsitnik.com/Span/#span-must-not-be-a-generic-type-argument
213226
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
@@ -273,6 +286,14 @@
273286
BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias);
274287
}
275288

289+
private void WorkloadActionSingleInvoke()
290+
{
291+
$LoadArguments$
292+
ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder;
293+
alias = workloadDelegate($PassArguments$);
294+
BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref alias);
295+
}
296+
276297
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
277298
public ref $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$()
278299
{
@@ -332,6 +353,14 @@
332353
BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias);
333354
}
334355

356+
private void WorkloadActionSingleInvoke()
357+
{
358+
$LoadArguments$
359+
ref $WorkloadMethodReturnType$ alias = ref workloadDefaultValueHolder;
360+
alias = workloadDelegate($PassArguments$);
361+
BenchmarkDotNet.Engines.DeadCodeEliminationHelper.KeepAliveWithoutBoxingReadonly(alias);
362+
}
363+
335364
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
336365
public ref readonly $WorkloadMethodReturnType$ $DisassemblerEntryMethodName$()
337366
{
@@ -381,6 +410,12 @@
381410
}
382411
}
383412

413+
private void WorkloadActionSingleInvoke()
414+
{
415+
$LoadArguments$
416+
workloadDelegate($PassArguments$);
417+
}
418+
384419
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization | System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
385420
public void $DisassemblerEntryMethodName$()
386421
{

0 commit comments

Comments
 (0)