This commit is contained in:
Andrey Akinshin 2016-09-23 14:27:18 +03:00
Родитель 2a6578034b
Коммит 03fb04c7a0
19 изменённых файлов: 194 добавлений и 55 удалений

Просмотреть файл

@ -41,7 +41,8 @@ In this category, you can specifiy how to benchmark each method.
* `WarmupCount`: how many warmup iterations should be performed
* `TargetCount`: how many target iterations should be performed
* `IterationTime`: desired time of a single iteration
* `InvocationCount`: count of invocation in a single iteration (if specified, `IterationTime` will be ignored)
* `UnrollFactor`: how many times the benchmark method will be invoked per one iteration of a generated loop
* `InvocationCount`: count of invocation in a single iteration (if specified, `IterationTime` will be ignored), must be a multiple of `UnrollFactor`
Usually, you shouldn't specify such characteristics like `LaunchCount`, `WarmupCount`, `TargetCount`, or `IterationTime` because BenchmarkDotNet has a smart algorithm to choose these values automatically based on recieved measurements. You can specify it for testing purposes or when you are damn sure that you know perfect characteristics for your benchmark (when you set `TargetCount` = `20` you should unserstand why `20` is a good value for your case).

Просмотреть файл

@ -0,0 +1,32 @@
using BenchmarkDotNet.Attributes;
namespace BenchmarkDotNet.Samples.Intro
{
public class IntroEmptyMethods
{
[Benchmark]
public void Empty1()
{
}
[Benchmark]
public void Empty2()
{
}
[Benchmark]
public void Empty3()
{
}
[Benchmark]
public void Empty4()
{
}
[Benchmark]
public void Empty5()
{
}
}
}

Просмотреть файл

@ -4,7 +4,8 @@ using BenchmarkDotNet.Attributes.Jobs;
namespace BenchmarkDotNet.Samples.Intro
{
[DryJob]
[ShortRunJob]
[KeepBenchmarkFiles()]
public class IntroJobsFull
{
[Benchmark(Baseline = true)]

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
@ -6,6 +7,7 @@ using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Core.Helpers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Running;
@ -18,7 +20,7 @@ namespace BenchmarkDotNet.Code
{
var provider = GetDeclarationsProvider(benchmark.Target);
return new SmartStringBuilder(ResourceHelper.LoadTemplate("BenchmarkProgram.txt")).
string text = new SmartStringBuilder(ResourceHelper.LoadTemplate("BenchmarkProgram.txt")).
Replace("$OperationsPerInvoke$", provider.OperationsPerInvoke).
Replace("$TargetTypeNamespace$", provider.TargetTypeNamespace).
Replace("$TargetMethodReturnTypeNamespace$", provider.TargetMethodReturnTypeNamespace).
@ -35,6 +37,29 @@ namespace BenchmarkDotNet.Code
Replace("$JobSetDefinition$", GetJobsSetDefinition(benchmark)).
Replace("$ParamsContent$", GetParamsContent(benchmark)).
ToString();
text = Unroll(text, benchmark.Job.Run.UnrollFactor.Resolve(EnvResolver.Instance));
return text;
}
private static string Unroll(string text, int factor)
{
const string unrollDirective = "@Unroll@";
var oldLines = text.Split('\n');
var newLines = new List<string>();
foreach (string line in oldLines)
{
if (line.Contains(unrollDirective))
{
string newLine = line.Replace(unrollDirective, "");
for (int i = 0; i < factor; i++)
newLines.Add(newLine);
}
else
newLines.Add(line);
}
return string.Join("\n", newLines);
}
private static string GetJobsSetDefinition(Benchmark benchmark)

Просмотреть файл

@ -48,6 +48,7 @@ namespace BenchmarkDotNet.Configs
{
yield return BaselineValidator.FailOnError;
yield return JitOptimizationsValidator.DontFailOnError;
yield return UnrollFactorValidator.Default;
}
public IEnumerable<Job> GetJobs() => Enumerable.Empty<Job>();

Просмотреть файл

@ -15,7 +15,7 @@ namespace BenchmarkDotNet.Engines
public class Engine : IEngine
{
public const int MinInvokeCount = 4;
public const int MinIterationTimeMs = 200;
public static readonly TimeInterval MinIterationTime = 200 * TimeInterval.Millisecond;
public Job TargetJob { get; set; } = Job.Default;
public long OperationsPerInvoke { get; set; } = 1;
@ -47,6 +47,7 @@ namespace BenchmarkDotNet.Engines
Jitting();
long invokeCount = 1;
int unrollFactor = TargetJob.Run.UnrollFactor.Resolve(Resolver);
IList<Measurement> idle = null;
if (TargetJob.Run.RunStrategy.Resolve(Resolver) != RunStrategy.ColdStart)
@ -55,13 +56,13 @@ namespace BenchmarkDotNet.Engines
if (TargetJob.Accuracy.EvaluateOverhead.Resolve(Resolver))
{
warmupStage.RunIdle(invokeCount);
idle = targetStage.RunIdle(invokeCount);
warmupStage.RunIdle(invokeCount, unrollFactor);
idle = targetStage.RunIdle(invokeCount, unrollFactor);
}
warmupStage.RunMain(invokeCount);
warmupStage.RunMain(invokeCount, unrollFactor);
}
var main = targetStage.RunMain(invokeCount);
var main = targetStage.RunMain(invokeCount, unrollFactor);
// TODO: Move calculation of the result measurements to a separated class
PrintResult(idle, main);
@ -78,7 +79,8 @@ namespace BenchmarkDotNet.Engines
private void PrintResult(IList<Measurement> idle, IList<Measurement> main)
{
// TODO: use Accuracy.RemoveOutliers
var overhead = idle == null ? 0.0 : new Statistics(idle.Select(m => m.Nanoseconds)).Median;
// TODO: check if resulted measurements are too small (like < 0.1ns)
double overhead = idle == null ? 0.0 : new Statistics(idle.Select(m => m.Nanoseconds)).Median;
int resultIndex = 0;
foreach (var measurement in main)
{
@ -97,7 +99,8 @@ namespace BenchmarkDotNet.Engines
{
// Initialization
long invokeCount = data.InvokeCount;
var totalOperations = invokeCount * OperationsPerInvoke;
int unrollFactor = data.UnrollFactor;
long totalOperations = invokeCount * OperationsPerInvoke;
var action = data.IterationMode.IsIdle() ? IdleAction : MainAction;
// Setup
@ -106,7 +109,7 @@ namespace BenchmarkDotNet.Engines
// Measure
var clock = TargetJob.Infrastructure.Clock.Resolve(Resolver).Start();
action(invokeCount);
action(invokeCount / unrollFactor);
var clockSpan = clock.Stop();
// Cleanup

Просмотреть файл

@ -28,9 +28,12 @@ namespace BenchmarkDotNet.Engines
/// </summary>
private long RunAuto()
{
long invokeCount = TargetAccuracy.MinInvokeCount.Resolve(Resolver);
int unrollFactor = TargetJob.Run.UnrollFactor.Resolve(Resolver);
Func<long, long> autocorrect = count => (count + unrollFactor - 1) / unrollFactor * unrollFactor;
long invokeCount = autocorrect(TargetAccuracy.MinInvokeCount.Resolve(Resolver));
double maxError = TargetAccuracy.MaxStdErrRelative.Resolve(Resolver); // TODO: introduce a StdErr factor
double minIterationTome = TimeUnit.Convert(Engine.MinIterationTimeMs, TimeUnit.Millisecond, TimeUnit.Nanosecond);
double minIterationTime = Engine.MinIterationTime.Nanoseconds;
double resolution = TargetClock.GetResolution().Nanoseconds;
@ -38,12 +41,12 @@ namespace BenchmarkDotNet.Engines
while (true)
{
iterationCounter++;
var measurement = RunIteration(IterationMode.Pilot, iterationCounter, invokeCount);
double iterationTime = measurement.Nanoseconds;
var measurement = RunIteration(IterationMode.Pilot, iterationCounter, invokeCount, unrollFactor);
double iterationTime = measurement.Nanoseconds;
double operationError = 2.0 * resolution / invokeCount; // An operation error which has arisen due to the Chronometer precision
double operationMaxError = iterationTime / invokeCount * maxError; // Max acceptable operation error
bool isFinished = operationError < operationMaxError && iterationTime >= minIterationTome;
bool isFinished = operationError < operationMaxError && iterationTime >= minIterationTime;
if (isFinished)
break;
if (invokeCount >= MaxInvokeCount)
@ -61,7 +64,10 @@ namespace BenchmarkDotNet.Engines
/// </summary>
private long RunSpecific()
{
long invokeCount = Engine.MinInvokeCount;
int unrollFactor = TargetJob.Run.UnrollFactor.Resolve(Resolver);
Func<long, long> autocorrect = count => (count + unrollFactor - 1) / unrollFactor * unrollFactor;
long invokeCount = autocorrect(Engine.MinInvokeCount);
double targetIterationTime = TargetJob.Run.IterationTime.Resolve(Resolver).ToNanoseconds();
int iterationCounter = 0;
@ -69,9 +75,9 @@ namespace BenchmarkDotNet.Engines
while (true)
{
iterationCounter++;
var measurement = RunIteration(IterationMode.Pilot, iterationCounter, invokeCount);
var measurement = RunIteration(IterationMode.Pilot, iterationCounter, invokeCount, unrollFactor);
double actualIterationTime = measurement.Nanoseconds;
long newInvokeCount = Math.Max(TargetAccuracy.MinInvokeCount.Resolve(Resolver), (long) Math.Round(invokeCount * targetIterationTime / actualIterationTime));
long newInvokeCount = autocorrect(Math.Max(TargetAccuracy.MinInvokeCount.Resolve(Resolver), (long) Math.Round(invokeCount * targetIterationTime / actualIterationTime)));
if (newInvokeCount < invokeCount)
downCount++;

Просмотреть файл

@ -1,4 +1,5 @@
using BenchmarkDotNet.Characteristics;
using System;
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Horology;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
@ -19,9 +20,11 @@ namespace BenchmarkDotNet.Engines
protected IClock TargetClock => engine.Resolver.Resolve(TargetJob.Infrastructure.Clock);
protected IResolver Resolver => engine.Resolver;
protected Measurement RunIteration(IterationMode mode, int index, long invokeCount)
protected Measurement RunIteration(IterationMode mode, int index, long invokeCount, int unrollFactor)
{
return engine.RunIteration(new IterationData(mode, index, invokeCount));
if (invokeCount % unrollFactor != 0)
throw new ArgumentOutOfRangeException($"InvokeCount({invokeCount}) should be a multiple of UnrollFactor({unrollFactor}).");
return engine.RunIteration(new IterationData(mode, index, invokeCount, unrollFactor));
}
protected void WriteLine() => engine.WriteLine();

Просмотреть файл

@ -18,17 +18,17 @@ namespace BenchmarkDotNet.Engines
{
}
public List<Measurement> Run(long invokeCount, IterationMode iterationMode, ICharacteristic<int> iterationCount)
public List<Measurement> Run(long invokeCount, IterationMode iterationMode, ICharacteristic<int> iterationCount, int unrollFactor)
{
return iterationCount.IsDefault
? RunAuto(invokeCount, iterationMode)
: RunSpecific(invokeCount, iterationMode, iterationCount.SpecifiedValue);
? RunAuto(invokeCount, iterationMode, unrollFactor)
: RunSpecific(invokeCount, iterationMode, iterationCount.SpecifiedValue, unrollFactor);
}
public List<Measurement> RunIdle(long invokeCount) => Run(invokeCount, IterationMode.IdleTarget, TargetJob.Run.TargetCount.MakeDefault());
public List<Measurement> RunMain(long invokeCount) => Run(invokeCount, IterationMode.MainTarget, TargetJob.Run.TargetCount);
public List<Measurement> RunIdle(long invokeCount, int unrollFactor) => Run(invokeCount, IterationMode.IdleTarget, TargetJob.Run.TargetCount.MakeDefault(), unrollFactor);
public List<Measurement> RunMain(long invokeCount, int unrollFactor) => Run(invokeCount, IterationMode.MainTarget, TargetJob.Run.TargetCount, unrollFactor);
private List<Measurement> RunAuto(long invokeCount, IterationMode iterationMode)
private List<Measurement> RunAuto(long invokeCount, IterationMode iterationMode, int unrollFactor)
{
var measurements = new List<Measurement>();
int iterationCounter = 0;
@ -37,7 +37,7 @@ namespace BenchmarkDotNet.Engines
while (true)
{
iterationCounter++;
var measurement = RunIteration(iterationMode, iterationCounter, invokeCount);
var measurement = RunIteration(iterationMode, iterationCounter, invokeCount, unrollFactor);
measurements.Add(measurement);
var statistics = new Statistics(measurements.Select(m => m.Nanoseconds));
@ -56,11 +56,11 @@ namespace BenchmarkDotNet.Engines
return measurements;
}
private List<Measurement> RunSpecific(long invokeCount, IterationMode iterationMode, int iterationCount)
private List<Measurement> RunSpecific(long invokeCount, IterationMode iterationMode, int iterationCount, int unrollFactor)
{
var measurements = new List<Measurement>();
for (int i = 0; i < iterationCount; i++)
measurements.Add(RunIteration(iterationMode, i + 1, invokeCount));
measurements.Add(RunIteration(iterationMode, i + 1, invokeCount, unrollFactor));
WriteLine();
return measurements;
}

Просмотреть файл

@ -15,24 +15,24 @@ namespace BenchmarkDotNet.Engines
{
}
public List<Measurement> Run(long invokeCount, IterationMode iterationMode, ICharacteristic<int> iterationCount)
public List<Measurement> Run(long invokeCount, IterationMode iterationMode, ICharacteristic<int> iterationCount, int unrollFactor)
{
return iterationCount.IsDefault
? RunAuto(invokeCount, iterationMode)
: RunSpecific(invokeCount, iterationMode, iterationCount.SpecifiedValue);
? RunAuto(invokeCount, iterationMode, unrollFactor)
: RunSpecific(invokeCount, iterationMode, iterationCount.SpecifiedValue, unrollFactor);
}
public void RunIdle(long invokeCount) => Run(invokeCount, IterationMode.IdleWarmup, TargetJob.Run.WarmupCount.MakeDefault());
public void RunMain(long invokeCount) => Run(invokeCount, IterationMode.MainWarmup, TargetJob.Run.WarmupCount);
public void RunIdle(long invokeCount, int unrollFactor) => Run(invokeCount, IterationMode.IdleWarmup, TargetJob.Run.WarmupCount.MakeDefault(), unrollFactor);
public void RunMain(long invokeCount, int unrollFactor) => Run(invokeCount, IterationMode.MainWarmup, TargetJob.Run.WarmupCount, unrollFactor);
private List<Measurement> RunAuto(long invokeCount, IterationMode iterationMode)
private List<Measurement> RunAuto(long invokeCount, IterationMode iterationMode, int unrollFactor)
{
int iterationCounter = 0;
var measurements = new List<Measurement>(MaxIterationCount);
while (true)
{
iterationCounter++;
measurements.Add(RunIteration(iterationMode, iterationCounter, invokeCount));
measurements.Add(RunIteration(iterationMode, iterationCounter, invokeCount, unrollFactor));
if (IsWarmupFinished(measurements, iterationMode))
break;
}
@ -40,11 +40,11 @@ namespace BenchmarkDotNet.Engines
return measurements;
}
private List<Measurement> RunSpecific(long invokeCount, IterationMode iterationMode, int iterationCount)
private List<Measurement> RunSpecific(long invokeCount, IterationMode iterationMode, int iterationCount, int unrollFactor)
{
var measurements = new List<Measurement>(iterationCount);
for (int i = 0; i < iterationCount; i++)
measurements.Add(RunIteration(iterationMode, i + 1, invokeCount));
measurements.Add(RunIteration(iterationMode, i + 1, invokeCount, unrollFactor));
WriteLine();
return measurements;
}

Просмотреть файл

@ -5,12 +5,14 @@
public IterationMode IterationMode { get; }
public int Index { get; }
public long InvokeCount { get; }
public int UnrollFactor { get; }
public IterationData(IterationMode iterationMode, int index, long invokeCount)
public IterationData(IterationMode iterationMode, int index, long invokeCount, int unrollFactor)
{
IterationMode = iterationMode;
Index = index;
InvokeCount = invokeCount;
UnrollFactor = unrollFactor;
}
}
}

Просмотреть файл

@ -19,6 +19,8 @@ namespace BenchmarkDotNet.Environments
// TODO: find a better place
var acc = Job.Default.Accuracy;
Register(acc.AnaylyzeLaunchVariance, () => false);
var run = Job.Default.Run;
Register(run.UnrollFactor, () => 16);
}
}
}

Просмотреть файл

@ -31,6 +31,7 @@ namespace BenchmarkDotNet.Jobs
public static Job WithTargetCount(this Job job, int count) => job.With(job.Run.TargetCount.Mutate(count));
public static Job WithIterationTime(this Job job, TimeInterval time) => job.With(job.Run.IterationTime.Mutate(time));
public static Job WithInvocationCount(this Job job, int count) => job.With(job.Run.InvocationCount.Mutate(count));
public static Job WithUnrollFactor(this Job job, int factor) => job.With(job.Run.UnrollFactor.Mutate(factor));
// Infrastructure
public static Job With(this Job job, IToolchain toolchain) => job.With(job.Infrastructure.Toolchain.Mutate(toolchain));

Просмотреть файл

@ -8,7 +8,7 @@ namespace BenchmarkDotNet.Jobs
{
public static readonly RunMode Default = new RunMode();
public static readonly JobMutator Dry = CreateMutator(nameof(Dry), 1, 1, 1, Engines.RunStrategy.ColdStart);
public static readonly JobMutator Dry = CreateMutator(nameof(Dry), 1, 1, 1, Engines.RunStrategy.ColdStart).Add(Default.UnrollFactor.Mutate(1));
public static readonly JobMutator Short = CreateMutator(nameof(Short), 1, 3, 3);
public static readonly JobMutator Medium = CreateMutator(nameof(Medium), 2, 10, 15);
public static readonly JobMutator Long = CreateMutator(nameof(Long), 3, 15, 100);
@ -17,22 +17,24 @@ namespace BenchmarkDotNet.Jobs
private static ICharacteristic<T> Create<T>(string id) => Characteristic<T>.Create("Run", id);
/// <summary>
/// RunStrategy
/// Available values: Throughput and ColdStart.
/// Throughput: default strategy which allows to get good precision level.
/// ColdStart: should be used only for measuring cold start of the application or testing purpose.
/// </summary>
public ICharacteristic<RunStrategy> RunStrategy { get; private set; } = Create<RunStrategy>(nameof(RunStrategy));
/// <summary>
/// LaunchCount
/// How many times we should launch process with target benchmark.
/// </summary>
public ICharacteristic<int> LaunchCount { get; private set; } = Create<int>(nameof(LaunchCount));
/// <summary>
/// WarmupCount
/// How many warmup iterations should be performed.
/// </summary>
public ICharacteristic<int> WarmupCount { get; private set; } = Create<int>(nameof(WarmupCount));
/// <summary>
/// TargetCount
/// How many target iterations should be performed
/// </summary>
public ICharacteristic<int> TargetCount { get; private set; } = Create<int>(nameof(TargetCount));
@ -42,10 +44,17 @@ namespace BenchmarkDotNet.Jobs
public ICharacteristic<TimeInterval> IterationTime { get; private set; } = Create<TimeInterval>(nameof(IterationTime));
/// <summary>
/// Invocation count in a single iteration. If specified, <see cref="IterationTime"/> will be ignored.
/// Invocation count in a single iteration.
/// If specified, <see cref="IterationTime"/> will be ignored.
/// If specified, it must be a multiple of <see cref="UnrollFactor"/>.
/// </summary>
public ICharacteristic<int> InvocationCount { get; private set; } = Create<int>(nameof(InvocationCount));
/// <summary>
/// How many times the benchmark method will be invoked per one iteration of a generated loop.
/// </summary>
public ICharacteristic<int> UnrollFactor { get; private set; }= Create<int>(nameof(UnrollFactor));
public static JobMutator CreateMutator(string id, int launchCount, int warmupCount, int targetCount,
RunStrategy strategy = Engines.RunStrategy.Throughput)
{
@ -66,6 +75,7 @@ namespace BenchmarkDotNet.Jobs
mode.TargetCount = mode.TargetCount.Mutate(set);
mode.IterationTime = mode.IterationTime.Mutate(set);
mode.InvocationCount = mode.InvocationCount.Mutate(set);
mode.UnrollFactor = mode.UnrollFactor.Mutate(set);
return mode;
}
@ -75,7 +85,8 @@ namespace BenchmarkDotNet.Jobs
WarmupCount,
TargetCount,
IterationTime,
InvocationCount
InvocationCount,
UnrollFactor
);
}
}

Просмотреть файл

@ -84,13 +84,17 @@ namespace BenchmarkDotNet.Autogenerated
private void IdleMultiAction(long invokeCount)
{
for (long i = 0; i < invokeCount; i++)
consumer.Consume(idleAction());
{
consumer.Consume(idleAction());@Unroll@
}
}
private void MainMultiAction(long invokeCount)
{
for (long i = 0; i < invokeCount; i++)
consumer.Consume(mainAction());
{
consumer.Consume(mainAction());@Unroll@
}
}
#else
@ -98,13 +102,17 @@ namespace BenchmarkDotNet.Autogenerated
private void IdleMultiAction(long invokeCount)
{
for (long i = 0; i < invokeCount; i++)
idleAction();
{
idleAction();@Unroll@
}
}
private void MainMultiAction(long invokeCount)
{
for (long i = 0; i < invokeCount; i++)
mainAction();
{
mainAction();@Unroll@
}
}
#endif

Просмотреть файл

@ -8,7 +8,8 @@ namespace BenchmarkDotNet.Validators
{
private static readonly IValidator[] MandatoryValidators =
{
BaselineValidator.FailOnError
BaselineValidator.FailOnError,
UnrollFactorValidator.Default
};
internal readonly IValidator[] Validators;

Просмотреть файл

@ -0,0 +1,42 @@
using System.Collections.Generic;
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Running;
namespace BenchmarkDotNet.Validators
{
public class UnrollFactorValidator : IValidator
{
public static readonly IValidator Default = new UnrollFactorValidator();
private UnrollFactorValidator()
{
}
public bool TreatsWarningsAsErrors => true;
public IEnumerable<ValidationError> Validate(IList<Benchmark> benchmarks)
{
var resolver = EnvResolver.Instance; // TODO: use specified resolver.
foreach (var benchmark in benchmarks)
{
var run = benchmark.Job.Run;
int unrollFactor = run.UnrollFactor.Resolve(resolver);
if (unrollFactor <= 0)
{
string message = $"Specified UnrollFactor ({unrollFactor}) must be greater than zero";
yield return new ValidationError(true, message, benchmark);
}
else if (!run.InvocationCount.IsDefault)
{
int invocationCount = run.InvocationCount.SpecifiedValue;
if (invocationCount % unrollFactor != 0)
{
string message = $"Specified InvocationCount ({invocationCount}) must be a multiple of UnrollFactor ({unrollFactor})";
yield return new ValidationError(true, message, benchmark);
}
}
}
}
}
}

Просмотреть файл

@ -37,7 +37,7 @@ namespace BenchmarkDotNet.Tests.Engine
max = min;
var job = Job.Default;
var stage = CreateStage(job, measure);
var measurements = stage.Run(1, mode, Characteristic<int>.Create(""));
var measurements = stage.Run(1, mode, Characteristic<int>.Create(""), 1);
int count = measurements.Count;
output.WriteLine($"MeasurementCount = {count} (Min= {min}, Max = {max})");
Assert.InRange(count, min, max);

Просмотреть файл

@ -58,7 +58,7 @@ namespace BenchmarkDotNet.Tests.Engine
max = min;
var job = Job.Default;
var stage = CreateStage(job, measure);
var measurements = stage.Run(1, mode, Characteristic<int>.Create(""));
var measurements = stage.Run(1, mode, Characteristic<int>.Create(""), 1);
int count = measurements.Count;
output.WriteLine($"MeasurementCount = {count} (Min= {min}, Max = {max})");
Assert.InRange(count, min, max);