Skip to content

Commit

Permalink
don't require extra run for DisassemblyDiagnoser, fixes dotnet#543, d…
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsitnik authored and alinasmirnova committed Sep 22, 2018
1 parent 1ed6170 commit 06b30fe
Show file tree
Hide file tree
Showing 22 changed files with 175 additions and 263 deletions.
7 changes: 6 additions & 1 deletion src/BenchmarkDotNet.Core/Configs/ConfigExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ public static class ConfigExtensions
public static ILogger GetCompositeLogger(this IConfig config) => new CompositeLogger(config.GetLoggers().ToArray());
public static IExporter GetCompositeExporter(this IConfig config) => new CompositeExporter(config.GetExporters().ToArray());
public static IDiagnoser GetCompositeDiagnoser(this IConfig config) => new CompositeDiagnoser(config.GetDiagnosers().ToArray());
public static IDiagnoser GetCompositeDiagnoser(this IConfig config, Benchmark benchmark, RunMode runMode) => new CompositeDiagnoser(config.GetDiagnosers().Where(d => d.GetRunMode(benchmark) == runMode).ToArray());

public static IDiagnoser GetCompositeDiagnoser(this IConfig config, Benchmark benchmark, RunMode runMode)
=> config.GetDiagnosers().Any(d => d.GetRunMode(benchmark) == runMode)
? new CompositeDiagnoser(config.GetDiagnosers().Where(d => d.GetRunMode(benchmark) == runMode).ToArray())
: null;

public static IAnalyser GetCompositeAnalyser(this IConfig config) => new CompositeAnalyser(config.GetAnalysers().ToArray());
public static IValidator GetCompositeValidator(this IConfig config) => new CompositeValidator(config.GetValidators().ToArray());

Expand Down
19 changes: 4 additions & 15 deletions src/BenchmarkDotNet.Core/Diagnosers/CompositeDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
Expand All @@ -15,10 +16,7 @@ public class CompositeDiagnoser : IDiagnoser
{
private readonly IDiagnoser[] diagnosers;

public CompositeDiagnoser(params IDiagnoser[] diagnosers)
{
this.diagnosers = diagnosers.Distinct().ToArray();
}
public CompositeDiagnoser(params IDiagnoser[] diagnosers) => this.diagnosers = diagnosers.Distinct().ToArray();

public RunMode GetRunMode(Benchmark benchmark) => throw new InvalidOperationException("Should never be called for Composite Diagnoser");

Expand All @@ -30,17 +28,8 @@ public IEnumerable<IExporter> Exporters
public IColumnProvider GetColumnProvider()
=> new CompositeColumnProvider(diagnosers.Select(d => d.GetColumnProvider()).ToArray());

public void BeforeAnythingElse(DiagnoserActionParameters parameters)
=> diagnosers.ForEach(diagnoser => diagnoser.BeforeAnythingElse(parameters));

public void AfterGlobalSetup(DiagnoserActionParameters parameters)
=> diagnosers.ForEach(diagnoser => diagnoser.AfterGlobalSetup(parameters));

public void BeforeMainRun(DiagnoserActionParameters parameters)
=> diagnosers.ForEach(diagnoser => diagnoser.BeforeMainRun(parameters));

public void BeforeGlobalCleanup(DiagnoserActionParameters parameters)
=> diagnosers.ForEach(diagnoser => diagnoser.BeforeGlobalCleanup(parameters));
public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
=> diagnosers.ForEach(diagnoser => diagnoser.Handle(signal, parameters));

public void ProcessResults(Benchmark benchmark, BenchmarkReport report)
=> diagnosers.ForEach(diagnoser => diagnoser.ProcessResults(benchmark, report));
Expand Down
29 changes: 12 additions & 17 deletions src/BenchmarkDotNet.Core/Diagnosers/DisassemblyDiagnoser.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Jobs;
Expand Down Expand Up @@ -33,27 +34,30 @@ public IConfigurableDiagnoser<DisassemblyDiagnoserConfig> Configure(DisassemblyD

public IReadOnlyDictionary<Benchmark, DisassemblyResult> Results => results;
public IEnumerable<string> Ids => new[] { nameof(DisassemblyDiagnoser) };
public IEnumerable<IExporter> Exporters => new IExporter[] { new CombinedDisassemblyExporter(Results), new RawDisassemblyExporter(Results), new PrettyDisassemblyExporter(Results) };

public IEnumerable<IExporter> Exporters
=> new IExporter[]
{
new CombinedDisassemblyExporter(Results),
new RawDisassemblyExporter(Results),
new PrettyDisassemblyExporter(Results)
};

public IColumnProvider GetColumnProvider() => EmptyColumnProvider.Instance;
public void BeforeAnythingElse(DiagnoserActionParameters parameters) { }
public void BeforeMainRun(DiagnoserActionParameters parameters) { }
public void BeforeGlobalCleanup(DiagnoserActionParameters parameters) { }

public RunMode GetRunMode(Benchmark benchmark)
{
if (ShouldUseWindowsDissasembler(benchmark))
return RunMode.ExtraRun;
return RunMode.NoOverhead;
if (ShouldUseMonoDisassembler(benchmark))
return RunMode.SeparateLogic;

return RunMode.None;
}

// method was already compiled and executed for the Warmup, we can attach to the process and do the job
public void AfterGlobalSetup(DiagnoserActionParameters parameters)
public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
{
if (ShouldUseWindowsDissasembler(parameters.Benchmark))
if (signal == HostSignal.AfterAll && ShouldUseWindowsDissasembler(parameters.Benchmark))
results.Add(
parameters.Benchmark,
windowsDisassembler.Dissasemble(parameters));
Expand All @@ -79,9 +83,6 @@ public IEnumerable<ValidationError> Validate(ValidationParameters validationPara
if (!RuntimeInformation.IsWindows() && !ShouldUseMonoDisassembler(benchmark))
yield return new ValidationError(false, "No Disassembler support, only Mono is supported for non-Windows OS", benchmark);

if (IsVeryShortRun(benchmark) && !ShouldUseMonoDisassembler(benchmark))
yield return new ValidationError(true, "No Job.Dry support for disassembler. Please use Job.Short");

if (benchmark.Job.Infrastructure.HasValue(InfrastructureMode.ToolchainCharacteristic)
&& benchmark.Job.Infrastructure.Toolchain is InProcessToolchain)
{
Expand All @@ -95,11 +96,5 @@ private bool ShouldUseMonoDisassembler(Benchmark benchmark)

private bool ShouldUseWindowsDissasembler(Benchmark benchmark)
=> !(benchmark.Job.Env.Runtime is MonoRuntime) && RuntimeInformation.IsWindows();

private bool IsVeryShortRun(Benchmark benchmark)
=> benchmark.Job.HasValue(Jobs.RunMode.LaunchCountCharacteristic)
&& (benchmark.Job.Run.LaunchCount < Jobs.RunMode.Short.LaunchCount
|| benchmark.Job.Run.WarmupCount < Jobs.RunMode.Short.WarmupCount
|| benchmark.Job.Run.TargetCount < Jobs.RunMode.Short.TargetCount);
}
}
37 changes: 2 additions & 35 deletions src/BenchmarkDotNet.Core/Diagnosers/IDiagnoser.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
Expand All @@ -8,22 +9,6 @@

namespace BenchmarkDotNet.Diagnosers
{
public enum RunMode : byte
{
/// <summary>
/// given diagnoser should not be executed for given benchmark
/// </summary>
None,
/// <summary>
/// needs extra run of the benchmark
/// </summary>
ExtraRun,
/// <summary>
/// implements some separate logic, that can be executed at any time
/// </summary>
SeparateLogic
}

public interface IDiagnoser
{
IEnumerable<string> Ids { get; }
Expand All @@ -34,25 +19,7 @@ public interface IDiagnoser

RunMode GetRunMode(Benchmark benchmark);

/// <summary>
/// before jitting, warmup
/// </summary>
void BeforeAnythingElse(DiagnoserActionParameters parameters);

/// <summary>
/// after globalSetup, before run
/// </summary>
void AfterGlobalSetup(DiagnoserActionParameters parameters);

/// <summary>
/// after globalSetup, warmup and pilot but before the main run
/// </summary>
void BeforeMainRun(DiagnoserActionParameters parameters);

/// <summary>
/// after run, before globalSleanup
/// </summary>
void BeforeGlobalCleanup(DiagnoserActionParameters parameters);
void Handle(HostSignal signal, DiagnoserActionParameters parameters);

void ProcessResults(Benchmark benchmark, BenchmarkReport report);

Expand Down
8 changes: 3 additions & 5 deletions src/BenchmarkDotNet.Core/Diagnosers/MemoryDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,15 @@ public class MemoryDiagnoser : IDiagnoser

// the following methods are left empty on purpose
// the action takes places in other process, and the values are gathered by Engine
public void BeforeAnythingElse(DiagnoserActionParameters _) { }
public void AfterGlobalSetup(DiagnoserActionParameters _) { }
public void BeforeMainRun(DiagnoserActionParameters _) { }
public void BeforeGlobalCleanup(DiagnoserActionParameters parameters) { }
public void Handle(HostSignal signal, DiagnoserActionParameters parameters) { }

public void DisplayResults(ILogger logger) { }

public void ProcessResults(Benchmark benchmark, BenchmarkReport report)
=> results.Add(benchmark, report.GcStats);

public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) => Enumerable.Empty<ValidationError>();
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
=> Array.Empty<ValidationError>();

public class AllocationColumn : IColumn
{
Expand Down
22 changes: 22 additions & 0 deletions src/BenchmarkDotNet.Core/Diagnosers/RunMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace BenchmarkDotNet.Diagnosers
{
public enum RunMode : byte
{
/// <summary>
/// given diagnoser should not be executed for given benchmark
/// </summary>
None,
/// <summary>
/// needs extra run of the benchmark
/// </summary>
ExtraRun,
/// <summary>
/// no overhead, can be executed without extra run
/// </summary>
NoOverhead,
/// <summary>
/// implements some separate logic, that can be executed at any time
/// </summary>
SeparateLogic
}
}
12 changes: 3 additions & 9 deletions src/BenchmarkDotNet.Core/Diagnosers/UnresolvedDiagnoser.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Reports;
Expand All @@ -20,21 +21,14 @@ public class UnresolvedDiagnoser : IDiagnoser

private readonly Type unresolved;

public UnresolvedDiagnoser(Type unresolved)
{
this.unresolved = unresolved;
}
public UnresolvedDiagnoser(Type unresolved) => this.unresolved = unresolved;

public RunMode GetRunMode(Benchmark benchmark) => RunMode.None;

public IEnumerable<string> Ids => Array.Empty<string>();
public IEnumerable<IExporter> Exporters => Array.Empty<IExporter>();
public IColumnProvider GetColumnProvider() => EmptyColumnProvider.Instance;

public void BeforeAnythingElse(DiagnoserActionParameters parameters) { }
public void AfterGlobalSetup(DiagnoserActionParameters parameters) { }
public void BeforeMainRun(DiagnoserActionParameters parameters) { }
public void BeforeGlobalCleanup(DiagnoserActionParameters parameters) { }
public void Handle(HostSignal signal, DiagnoserActionParameters parameters) { }
public void ProcessResults(Benchmark benchmark, BenchmarkReport report) { }

public void DisplayResults(ILogger logger) => logger.WriteLineError(GetErrorMessage());
Expand Down
39 changes: 15 additions & 24 deletions src/BenchmarkDotNet.Core/Engines/ConsoleHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ namespace BenchmarkDotNet.Engines
public sealed class ConsoleHost : IHost
{
private readonly TextWriter outWriter;
private readonly TextReader inReader;

public ConsoleHost([NotNull] TextWriter outWriter, bool hasDiagnoserAttached)
public ConsoleHost([NotNull]TextWriter outWriter, [NotNull]TextReader inReader, bool hasDiagnoserAttached)
{
if (outWriter == null)
throw new ArgumentNullException(nameof(outWriter));
this.outWriter = outWriter;
this.outWriter = outWriter ?? throw new ArgumentNullException(nameof(outWriter));
this.inReader = inReader ?? throw new ArgumentNullException(nameof(inReader));
IsDiagnoserAttached = hasDiagnoserAttached;
}

Expand All @@ -26,26 +26,17 @@ public ConsoleHost([NotNull] TextWriter outWriter, bool hasDiagnoserAttached)

public void SendSignal(HostSignal hostSignal)
{
switch (hostSignal)
{
case HostSignal.BeforeAnythingElse:
WriteLine(Engine.Signals.BeforeAnythingElse);
break;
case HostSignal.AfterGlobalSetup:
WriteLine(Engine.Signals.AfterGlobalSetup);
break;
case HostSignal.BeforeMainRun:
WriteLine(Engine.Signals.BeforeMainRun);
break; ;
case HostSignal.BeforeGlobalCleanup:
WriteLine(Engine.Signals.BeforeGlobalCleanup);
break;
case HostSignal.AfterAnythingElse:
WriteLine(Engine.Signals.AfterAnythingElse);
break;
default:
throw new ArgumentOutOfRangeException(nameof(hostSignal), hostSignal, null);
}
if(!IsDiagnoserAttached) // no need to send the signal, nobody is listening for it
return;

WriteLine(Engine.Signals.ToMessage(hostSignal));

// read the response from Parent process, make the communication blocking
// I did not use Mutexes because they are not supported for Linux/MacOs for .NET Core
// this solution is stupid simple and it works
var acknowledgment = inReader.ReadLine();
if(acknowledgment != Engine.Signals.Acknowledgment)
throw new NotSupportedException($"Unknown Acknowledgment: {acknowledgment}");
}

public void ReportResults(RunResults runResults) => runResults.Print(outWriter);
Expand Down
37 changes: 30 additions & 7 deletions src/BenchmarkDotNet.Core/Engines/Engine.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Horology;
using BenchmarkDotNet.Jobs;
Expand Down Expand Up @@ -89,13 +90,18 @@ public void PreAllocate()

public void Jitting()
{
GlobalSetupAction?.Invoke();
IterationSetupAction?.Invoke();

// first signal about jitting is raised from auto-generated Program.cs, look at BenchmarkProgram.txt
Dummy1Action.Invoke();
MainAction.Invoke(1);
Dummy2Action.Invoke();
IdleAction.Invoke(1);
Dummy3Action.Invoke();
isJitted = true;

IterationCleanupAction?.Invoke();
}

public RunResults Run()
Expand Down Expand Up @@ -126,7 +132,9 @@ public RunResults Run()

// we enable monitoring after pilot & warmup, just to ignore the memory allocated by these runs
EnableMonitoring();
if(IsDiagnoserAttached) Host.BeforeMainRun();

Host.BeforeMainRun();

forcedFullGarbageCollections = 0; // zero it in case the Engine instance is reused (InProcessToolchain)
var initialGcStats = GcStats.ReadInitial(IsDiagnoserAttached);

Expand All @@ -136,6 +144,8 @@ public RunResults Run()
var forcedCollections = GcStats.FromForced(forcedFullGarbageCollections);
var workGcHasDone = finalGcStats - forcedCollections - initialGcStats;

Host.AfterMainRun();

bool removeOutliers = TargetJob.ResolveValue(AccuracyMode.RemoveOutliersCharacteristic, Resolver);

return new RunResults(idle, main, removeOutliers, workGcHasDone);
Expand Down Expand Up @@ -220,14 +230,27 @@ private void EnsureNothingIsPrintedWhenDiagnoserIsAttached()
}

[UsedImplicitly]
public class Signals
public static class Signals
{
public const string BeforeAnythingElse = "// BeforeAnythingElse";
public const string AfterGlobalSetup = "// AfterGlobalSetup";
public const string BeforeMainRun = "// BeforeMainRun";
public const string BeforeGlobalCleanup = "// BeforeGlobalCleanup";
public const string AfterAnythingElse = "// AfterAnythingElse";
public const string DiagnoserIsAttachedParam = "diagnoserAttached";
public const string Acknowledgment = "Acknowledgment";

private static readonly Dictionary<HostSignal, string> SignalsToMessages
= new Dictionary<HostSignal, string>
{
{ HostSignal.BeforeAnythingElse, "// BeforeAnythingElse" },
{ HostSignal.BeforeMainRun, "// BeforeMainRun" },
{ HostSignal.AfterMainRun, "// AfterMainRun" },
{ HostSignal.AfterAll, "// AfterAll" }
};

private static readonly Dictionary<string, HostSignal> MessagesToSignals
= SignalsToMessages.ToDictionary(p => p.Value, p => p.Key);

public static string ToMessage(HostSignal signal) => SignalsToMessages[signal];

public static bool TryGetSignal(string message, out HostSignal signal)
=> MessagesToSignals.TryGetValue(message, out signal);
}
}
}
Loading

0 comments on commit 06b30fe

Please sign in to comment.