Skip to content

Survived Memory #1596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/BenchmarkDotNet/Attributes/MemoryDiagnoserAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ public class MemoryDiagnoserAttribute : Attribute, IConfigSource
public IConfig Config { get; }

/// <param name="displayGenColumns">Display Garbage Collections per Generation columns (Gen 0, Gen 1, Gen 2). True by default.</param>
public MemoryDiagnoserAttribute(bool displayGenColumns = true)
/// <param name="includeSurvived">If true, monitoring will be enabled and survived memory will be measured on the first benchmark run.</param>
public MemoryDiagnoserAttribute(bool displayGenColumns = true, bool includeSurvived = false)
{
Config = ManualConfig.CreateEmpty().AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig(displayGenColumns)));
Config = ManualConfig.CreateEmpty().AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig(displayGenColumns, includeSurvived)));
}
}
}
1 change: 1 addition & 0 deletions src/BenchmarkDotNet/Code/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ internal static string Generate(BuildPartition buildPartition)
.Replace("$PassArguments$", passArguments)
.Replace("$EngineFactoryType$", GetEngineFactoryTypeName(benchmark))
.Replace("$MeasureExtraStats$", buildInfo.Config.HasExtraStatsDiagnoser() ? "true" : "false")
.Replace("$MeasureSurvivedMemory$", buildInfo.Config.HasSurvivedMemoryDiagnoser() ? "true" : "false")
.Replace("$DisassemblerEntryMethodName$", DisassemblerConstants.DisassemblerEntryMethodName)
.Replace("$WorkloadMethodCall$", provider.GetWorkloadMethodCall(passArguments))
.RemoveRedundantIfDefines(compilationId);
Expand Down
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Configs/ImmutableConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ internal ImmutableConfig(

public bool HasMemoryDiagnoser() => diagnosers.OfType<MemoryDiagnoser>().Any();

public bool HasSurvivedMemoryDiagnoser() => diagnosers.Any(diagnoser => diagnoser is MemoryDiagnoser md && md.Config.IncludeSurvived);

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

public bool HasExceptionDiagnoser() => diagnosers.Contains(ExceptionDiagnoser.Default);
Expand Down
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public bool UseDisassemblyDiagnoser
[Option('a', "artifacts", Required = false, HelpText = "Valid path to accessible directory")]
public DirectoryInfo ArtifactsDirectory { get; set; }

[Option("memorySurvived", Required = false, Default = false, HelpText = "Measures survived memory.")]
public bool UseSurvivedMemoryDiagnoser { get; set; }

[Option("outliers", Required = false, Default = OutlierMode.RemoveUpper, HelpText = "DontRemove/RemoveUpper/RemoveLower/RemoveAll")]
public OutlierMode Outliers { get; set; }

Expand Down
5 changes: 4 additions & 1 deletion src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,11 @@ private static IConfig CreateConfig(CommandLineOptions options, IConfig globalCo
.Select(counterName => (HardwareCounter)Enum.Parse(typeof(HardwareCounter), counterName, ignoreCase: true))
.ToArray());

if (options.UseMemoryDiagnoser)
if (options.UseSurvivedMemoryDiagnoser)
config.AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig(includeSurvived: true)));
else if (options.UseMemoryDiagnoser)
config.AddDiagnoser(MemoryDiagnoser.Default);

if (options.UseThreadingDiagnoser)
config.AddDiagnoser(ThreadingDiagnoser.Default);
if (options.UseExceptionDiagnoser)
Expand Down
21 changes: 21 additions & 0 deletions src/BenchmarkDotNet/Diagnosers/MemoryDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using Perfolizer.Metrology;

namespace BenchmarkDotNet.Diagnosers
{
Expand Down Expand Up @@ -42,6 +43,26 @@ public IEnumerable<Metric> ProcessResults(DiagnoserResults diagnoserResults)
}

yield return new Metric(AllocatedMemoryMetricDescriptor.Instance, diagnoserResults.GcStats.GetBytesAllocatedPerOperation(diagnoserResults.BenchmarkCase) ?? double.NaN);

if (Config.IncludeSurvived)
{
yield return new Metric(SurvivedMemoryMetricDescriptor.Instance, diagnoserResults.GcStats.SurvivedBytes ?? double.NaN);
}
}

private class SurvivedMemoryMetricDescriptor : IMetricDescriptor
{
internal static readonly IMetricDescriptor Instance = new SurvivedMemoryMetricDescriptor();

public string Id => "Survived Memory";
public string DisplayName => "Survived";
public string Legend => "Memory survived after the first operation (managed only, inclusive, 1KB = 1024B)";
public string NumberFormat => "N0";
public UnitType UnitType => UnitType.Size;
public string Unit => SizeUnit.B.Abbreviation;
public bool TheGreaterTheBetter => false;
public int PriorityInCategory { get; } = AllocatedMemoryMetricDescriptor.Instance.PriorityInCategory + 1;
public bool GetIsAvailable(Metric metric) => true;
}

private class GarbageCollectionsMetricDescriptor : IMetricDescriptor
Expand Down
5 changes: 4 additions & 1 deletion src/BenchmarkDotNet/Diagnosers/MemoryDiagnoserConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ namespace BenchmarkDotNet.Diagnosers
public class MemoryDiagnoserConfig
{
/// <param name="displayGenColumns">Display Garbage Collections per Generation columns (Gen 0, Gen 1, Gen 2). True by default.</param>
/// <param name="includeSurvived">If true, monitoring will be enabled and survived memory will be measured on the first benchmark run.</param>
[PublicAPI]
public MemoryDiagnoserConfig(bool displayGenColumns = true)
public MemoryDiagnoserConfig(bool displayGenColumns = true, bool includeSurvived = false)
{
DisplayGenColumns = displayGenColumns;
IncludeSurvived = includeSurvived;
}

public bool DisplayGenColumns { get; }
public bool IncludeSurvived { get; }
}
}
38 changes: 31 additions & 7 deletions src/BenchmarkDotNet/Engines/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class Engine : IEngine

[PublicAPI] public IHost Host { get; }
[PublicAPI] public Action<long> WorkloadAction { get; }
[PublicAPI] public Action<long> WorkloadActionNoUnroll { get; }
[PublicAPI] public Action Dummy1Action { get; }
[PublicAPI] public Action Dummy2Action { get; }
[PublicAPI] public Action Dummy3Action { get; }
Expand All @@ -44,19 +45,22 @@ public class Engine : IEngine
private readonly EnginePilotStage pilotStage;
private readonly EngineWarmupStage warmupStage;
private readonly EngineActualStage actualStage;
private readonly bool includeExtraStats;
private readonly Random random;
private readonly bool includeExtraStats, includeSurvivedMemory;

private long? survivedBytes;
private bool survivedBytesMeasured;

internal Engine(
IHost host,
IResolver resolver,
Action dummy1Action, Action dummy2Action, Action dummy3Action, Action<long> overheadAction, Action<long> workloadAction, Job targetJob,
Action dummy1Action, Action dummy2Action, Action dummy3Action, Action<long> overheadAction, Action<long> workloadAction, Action<long> workloadActionNoUnroll, Job targetJob,
Action globalSetupAction, Action globalCleanupAction, Action iterationSetupAction, Action iterationCleanupAction, long operationsPerInvoke,
bool includeExtraStats, string benchmarkName)
bool includeExtraStats, bool includeSurvivedMemory, string benchmarkName)
{

Host = host;
OverheadAction = overheadAction;
WorkloadActionNoUnroll = workloadActionNoUnroll;
Dummy1Action = dummy1Action;
Dummy2Action = dummy2Action;
Dummy3Action = dummy3Action;
Expand All @@ -69,6 +73,7 @@ internal Engine(
OperationsPerInvoke = operationsPerInvoke;
this.includeExtraStats = includeExtraStats;
BenchmarkName = benchmarkName;
this.includeSurvivedMemory = includeSurvivedMemory;

Resolver = resolver;

Expand All @@ -86,6 +91,14 @@ internal Engine(
random = new Random(12345); // we are using constant seed to try to get repeatable results
}

internal Engine WithInitialData(Engine other)
{
// Copy the survived bytes from the other engine so we only measure it once.
survivedBytes = other.survivedBytes;
survivedBytesMeasured = other.survivedBytesMeasured;
return this;
}

public void Dispose()
{
try
Expand Down Expand Up @@ -166,6 +179,17 @@ public Measurement RunIteration(IterationData data)
if (EngineEventSource.Log.IsEnabled())
EngineEventSource.Log.IterationStart(data.IterationMode, data.IterationStage, totalOperations);

bool needsSurvivedMeasurement = includeSurvivedMemory && !isOverhead && !survivedBytesMeasured;
if (needsSurvivedMeasurement && GcStats.InitTotalBytes())
{
// Measure survived bytes for only the first invocation.
survivedBytesMeasured = true;
long beforeBytes = GcStats.GetTotalBytes();
WorkloadActionNoUnroll(1);
long afterBytes = GcStats.GetTotalBytes();
survivedBytes = afterBytes - beforeBytes;
}

var clockSpan = randomizeMemory
? MeasureWithRandomMemory(action, invokeCount / unrollFactor)
: Measure(action, invokeCount / unrollFactor);
Expand Down Expand Up @@ -235,8 +259,8 @@ private ClockSpan Measure(Action<long> action, long invokeCount)
IterationCleanupAction(); // we run iteration cleanup after collecting GC stats

var totalOperationsCount = data.InvokeCount * OperationsPerInvoke;
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperations(totalOperationsCount);
ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke);
GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperationsAndSurvivedBytes(totalOperationsCount, survivedBytes);
ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(totalOperationsCount);

return (gcStats, threadingStats, exceptionsStats.ExceptionsCount / (double)totalOperationsCount);
}
Expand Down Expand Up @@ -267,7 +291,7 @@ private void GcCollect()
ForceGcCollect();
}

private static void ForceGcCollect()
internal static void ForceGcCollect()
{
GC.Collect();
GC.WaitForPendingFinalizers();
Expand Down
8 changes: 6 additions & 2 deletions src/BenchmarkDotNet/Engines/EngineFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,12 @@ public IEngine CreateReadyToRun(EngineParameters engineParameters)
.WithMinInvokeCount(2) // the minimum is 2 (not the default 4 which can be too much and not 1 which we already know is not enough)
.WithEvaluateOverhead(false); // it's something very time consuming, it overhead is too small compared to total time

return CreateEngine(engineParameters, needsPilot, engineParameters.OverheadActionNoUnroll, engineParameters.WorkloadActionNoUnroll);
return CreateEngine(engineParameters, needsPilot, engineParameters.OverheadActionNoUnroll, engineParameters.WorkloadActionNoUnroll)
.WithInitialData(singleActionEngine);
}

var multiActionEngine = CreateMultiActionEngine(engineParameters);
var multiActionEngine = CreateMultiActionEngine(engineParameters)
.WithInitialData(singleActionEngine);

DeadCodeEliminationHelper.KeepAliveWithoutBoxing(Jit(multiActionEngine, ++jitIndex, invokeCount: defaultUnrollFactor, unrollFactor: defaultUnrollFactor));

Expand Down Expand Up @@ -118,13 +120,15 @@ private static Engine CreateEngine(EngineParameters engineParameters, Job job, A
engineParameters.Dummy3Action,
idle,
main,
engineParameters.WorkloadActionNoUnroll,
job,
engineParameters.GlobalSetupAction,
engineParameters.GlobalCleanupAction,
engineParameters.IterationSetupAction,
engineParameters.IterationCleanupAction,
engineParameters.OperationsPerInvoke,
engineParameters.MeasureExtraStats,
engineParameters.MeasureSurvivedMemory,
engineParameters.BenchmarkName);
}
}
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Engines/EngineParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public class EngineParameters
public Action IterationCleanupAction { get; set; }
public bool MeasureExtraStats { get; set; }

public bool MeasureSurvivedMemory { get; set; }

[PublicAPI] public string BenchmarkName { get; set; }

public bool NeedsJitting => TargetJob.ResolveValue(RunMode.RunStrategyCharacteristic, DefaultResolver).NeedsJitting();
Expand Down
Loading
Loading