Skip to content
Merged
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
66 changes: 13 additions & 53 deletions benchmarks/Clockwork.Benchmarks/DeterministicSchedulerBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using BenchmarkDotNet.Attributes;
using Clockwork.Runtime.Execution;
using Clockwork.Runtime.Scheduling;

namespace Clockwork.Benchmarks;

[MemoryDiagnoser]
Expand All @@ -14,66 +11,29 @@ public class DeterministicSchedulerBenchmarks

[Benchmark(Baseline = true, OperationsPerInvoke = SchedulingPointCount)]
public int Direct()
{
var workload = new Workload(scheduler: null, _initialCompletedSteps);
for (var operation = 0; operation < OperationCount; operation++)
{
workload.Run();
}

return workload.CompletedSteps;
}
=> SchedulerBenchmarkWorkload.RunDirect(
OperationCount,
StepsPerOperation,
_initialCompletedSteps);

[Benchmark(OperationsPerInvoke = SchedulingPointCount)]
public int DeterministicScheduler() => RunScheduler(_initialCompletedSteps);
public int DeterministicScheduler() =>
SchedulerBenchmarkWorkload.RunScheduler(
OperationCount,
StepsPerOperation,
_initialCompletedSteps);

public static int RunTrace(int iterationCount)
{
var completed = 0;
for (var iteration = 0; iteration < iterationCount; iteration++)
{
completed += RunScheduler(initialCompletedSteps: 0);
completed += SchedulerBenchmarkWorkload.RunScheduler(
OperationCount,
StepsPerOperation,
initialCompletedSteps: 0);
}

return completed;
}

private static int RunScheduler(int initialCompletedSteps)
{
using var scheduler = new SimulationScheduler(
new SimulationRuntimeIdentity(Guid.Empty, Seed: 1, Description: "benchmark"));
var workload = new Workload(scheduler, initialCompletedSteps);
Action body = workload.Run;

for (var operation = 0; operation < OperationCount; operation++)
{
scheduler.Schedule("benchmark", body);
}

int dispatched = scheduler.Drain(CancellationToken.None);
if (dispatched != SchedulingPointCount)
{
throw new InvalidOperationException(
$"Expected {SchedulingPointCount} dispatches but observed {dispatched}.");
}

return workload.CompletedSteps;
}

private sealed class Workload(SimulationScheduler? scheduler, int initialCompletedSteps)
{
public int CompletedSteps { get; private set; } = initialCompletedSteps;

public void Run()
{
for (var step = 0; step < StepsPerOperation; step++)
{
CompletedSteps++;
if (step + 1 < StepsPerOperation)
{
scheduler?.Yield();
}
}
}
}
}
64 changes: 64 additions & 0 deletions benchmarks/Clockwork.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,68 @@
return;
}

if (args is ["--trace-scaling", var scalingIterationCount, var operationCount]
&& int.TryParse(scalingIterationCount, out var scalingIterations)
&& scalingIterations > 0
&& int.TryParse(operationCount, out var operations)
&& operations > 0
&& 4096 % operations == 0)
{
Console.WriteLine(SchedulerScalingBenchmarks.RunTrace(scalingIterations, operations));
return;
}

if (args is ["--trace-readiness", var readinessIterationCount, var pendingWaitCount]
&& int.TryParse(readinessIterationCount, out var readinessIterations)
&& readinessIterations > 0
&& int.TryParse(pendingWaitCount, out var pendingWaits)
&& pendingWaits >= 0)
{
Console.WriteLine(SchedulerReadinessBenchmarks.RunTrace(readinessIterations, pendingWaits));
return;
}

if (args is ["--trace-replay", var replayIterationCount, var replayOperationCount, var replayMode]
&& int.TryParse(replayIterationCount, out var replayIterations)
&& replayIterations > 0
&& int.TryParse(replayOperationCount, out var replayOperations)
&& replayOperations > 0
&& 4096 % replayOperations == 0
&& replayMode is "record" or "replay")
{
Console.WriteLine(
ReplayRunnerBenchmarks.RunTrace(
replayIterations,
replayOperations,
replay: replayMode == "replay"));
return;
}

if (args is ["--trace-timers", var timerIterationCount, var timerCount, var timerMode]
&& int.TryParse(timerIterationCount, out var timerIterations)
&& timerIterations > 0
&& int.TryParse(timerCount, out var timers)
&& timers > 0
&& timerMode is "all" or "individual")
{
Console.WriteLine(
SimulationTimerQueueBenchmarks.RunTrace(
timerIterations,
timers,
advanceIndividually: timerMode == "individual"));
return;
}

if (args is ["--trace-decisions", var decisionIterationCount, var decisionMode]
&& int.TryParse(decisionIterationCount, out var decisionIterations)
&& decisionIterations > 0
&& decisionMode is "none" or "log")
{
Console.WriteLine(
SchedulerDecisionLogBenchmarks.RunTrace(
decisionIterations,
captureDecisions: decisionMode == "log"));
return;
}

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
54 changes: 54 additions & 0 deletions benchmarks/Clockwork.Benchmarks/RaceTrackerBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using BenchmarkDotNet.Attributes;
using System.Diagnostics.CodeAnalysis;
using Clockwork.Runtime.Execution;
using Clockwork.Runtime.Racing;
using Clockwork.Runtime.Scheduling;

namespace Clockwork.Benchmarks;

[MemoryDiagnoser]
[SuppressMessage(
"Design",
"CA1001:Types that own disposable fields should be disposable",
Justification = "BenchmarkDotNet invokes GlobalCleanup after each benchmark case.")]
public class RaceTrackerBenchmarks
{
private static readonly RaceMemoryLocation s_location =
new(RaceMemoryLocationKind.StaticField, 0, "Benchmark::Value");

private static readonly RaceSourceLocation s_source =
new("Benchmark.Write", 0, SourceFile: null, SourceLine: -1);

private SimulationScheduler _scheduler = null!;
private SimulationOperation _operation = null!;
private RaceTracker _tracker = null!;

[Params(0, 4, 16)]
public int HeldSynchronizationCount { get; set; }

[GlobalSetup]
public void Setup()
{
_scheduler = new SimulationScheduler(
new SimulationRuntimeIdentity(Guid.Empty, Seed: 1, Description: "benchmark"));
_operation = _scheduler.Register("benchmark", static () => { });
_tracker = new RaceTracker();
_tracker.RegisterOperation(_operation, parent: null);
for (var index = 0; index < HeldSynchronizationCount; index++)
{
_tracker.EnterSynchronization(_operation, new object());
}
}

[GlobalCleanup]
public void Cleanup() => _scheduler.Dispose();

[Benchmark]
public void RecordWrite() =>
_tracker.RecordAccess(
_operation,
RaceAccessKind.Write,
s_location,
s_source,
Array.Empty<RaceSchedulingPoint>());
}
107 changes: 107 additions & 0 deletions benchmarks/Clockwork.Benchmarks/ReplayRunnerBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using BenchmarkDotNet.Attributes;
using Clockwork.Runtime.Replay;
using Clockwork.Runtime.Scheduling;

namespace Clockwork.Benchmarks;

[MemoryDiagnoser]
public class ReplayRunnerBenchmarks
{
private const int DispatchCount = 4096;
private static readonly ReplayRecordingOptions s_recordingOptions = new()
{
RootSeed = 1,
SchedulingPolicy = ReplaySchedulingPolicy.RoundRobin,
MaxSteps = DispatchCount + 1,
};

private static readonly ReplayCompatibilityRequirements s_compatibility =
ReplayCompatibilityRequirements.Current();

private ReplayArtifact _artifact = null!;

[Params(4, 64, 256)]
public int OperationCount { get; set; }

[GlobalSetup]
public void Setup() => _artifact = Record(OperationCount).Artifact;

[Benchmark]
public int RecordScenario() => Record(OperationCount).Steps;

[Benchmark]
public int ReplayScenario() => Replay(_artifact, OperationCount).Steps;

public static int RunTrace(int iterationCount, int operationCount, bool replay)
{
ReplayArtifact? artifact = replay ? Record(operationCount).Artifact : null;
var completedSteps = 0;
for (var iteration = 0; iteration < iterationCount; iteration++)
{
completedSteps += replay
? Replay(artifact!, operationCount).Steps
: Record(operationCount).Steps;
}

return completedSteps;
}

private static ReplayExecutionResult Record(int operationCount)
{
var scenario = new YieldingReplayScenario(operationCount, DispatchCount / operationCount);
ReplayExecutionResult result = ReplayRunner.Record(
s_recordingOptions,
scenario.Schedule,
CancellationToken.None);
ValidateSteps(result);
return result;
}

private static ReplayExecutionResult Replay(ReplayArtifact artifact, int operationCount)
{
var scenario = new YieldingReplayScenario(operationCount, DispatchCount / operationCount);
ReplayExecutionResult result = ReplayRunner.Replay(
artifact,
s_compatibility,
scenario.Schedule,
maxSteps: DispatchCount + 1,
cancellationToken: CancellationToken.None);
ValidateSteps(result);
return result;
}

private static void ValidateSteps(ReplayExecutionResult result)
{
if (result.Steps != DispatchCount)
{
throw new InvalidOperationException(
$"Expected {DispatchCount} replay dispatches but observed {result.Steps}.");
}
}

private sealed class YieldingReplayScenario(int operationCount, int stepsPerOperation)
{
private SimulationScheduler _scheduler = null!;

public void Schedule(SimulationScheduler scheduler)
{
_scheduler = scheduler;
Action body = Run;
for (var operation = 0; operation < operationCount; operation++)
{
scheduler.Schedule("benchmark", body);
}
}

private void Run()
{
for (var step = 0; step < stepsPerOperation; step++)
{
if (step + 1 < stepsPerOperation)
{
_scheduler.Yield();
}
}
}
}
}
19 changes: 19 additions & 0 deletions benchmarks/Clockwork.Benchmarks/SchedulerAllocationBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using BenchmarkDotNet.Attributes;

namespace Clockwork.Benchmarks;

[MemoryDiagnoser]
public class SchedulerAllocationBenchmarks
{
private const int OperationCount = 4;

[Params(2, 32, 512, 4096)]
public int StepsPerOperation { get; set; }

[Benchmark]
public int DeterministicScheduler() =>
SchedulerBenchmarkWorkload.RunScheduler(
OperationCount,
StepsPerOperation,
initialCompletedSteps: 1);
}
Loading