Skip to content

Commit

Permalink
Fix deadlock and reduce allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
emelrad12 committed Nov 26, 2024
1 parent 8a436f6 commit 4281a63
Show file tree
Hide file tree
Showing 9 changed files with 435 additions and 57 deletions.
132 changes: 114 additions & 18 deletions JobScheduler.Benchmarks/Benchmark.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.CsProj;
using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
Expand All @@ -8,35 +9,130 @@

namespace Arch.Benchmarks;

public class Benchmark
public class HeavyCalculationJob : IJob
{
private static void Main(string[] args)
private double _first;
private double _second;

public HeavyCalculationJob(int first, int second)
{
var config = DefaultConfig.Instance.AddJob(Job.Default
.WithWarmupCount(2) // Anzahl der Warmup-Runden
.WithIterationCount(10) // Iterationen pro Benchmark
.WithMaxIterationCount(50) // Max. Iterationen
);
_first = first;
_second = second;
}

public void Execute()
{
for (var i = 0; i < 2000; i++)
{
_first = double.Sqrt(_second);
_second = double.Sqrt(_first) + 1;
}
}
}

for (var sindex = 0; sindex < 1_000; sindex++)
public class Benchmark
{
private static void BenchA()
{
var timer = Stopwatch.StartNew();
using var jobScheduler = new JobScheduler();
for (var sindex = 0; sindex < 100; sindex++)
{
var jobScheduler = new JobScheduler();
var handles = new List<JobHandle>(100);
var generation = jobScheduler.GetNewGeneration();
for (var index = 0; index < 20000; index++)
{
var job = new HeavyCalculationJob(index, index);
jobScheduler.ScheduleAndFlushPooledJobWithGeneration(job, generation);
}

jobScheduler.AwaitForGeneration(generation);
}

var time = timer.ElapsedMilliseconds;
Console.WriteLine($"Time: {time}ms");
}

for (var index = 0; index < 18; index++)
private static void BenchB()
{
using var jobScheduler = new JobScheduler();
var timer = Stopwatch.StartNew();
for (var sindex = 0; sindex < 100; sindex++)
{
List<JobHandle> _jobHandles = new();
for (var index = 0; index < 20000; index++)
{
var job = new CalculationJob(index, index);
var job = new HeavyCalculationJob(index, index);
var handle = jobScheduler.Schedule(job);
handles.Add(handle);
Console.WriteLine($"Handle {index} scheduled");
_jobHandles.Add(handle);
}

jobScheduler.Flush(_jobHandles.AsSpan());
jobScheduler.Wait(_jobHandles.AsSpan());
}

var time = timer.ElapsedMilliseconds;
Console.WriteLine($"Job scheduler Time: {time}ms");
}

private static void BenchC()
{
var timer = Stopwatch.StartNew();
for (var sindex = 0; sindex < 100; sindex++)
{
var list = new List<HeavyCalculationJob>();
for (var index = 0; index < 20000; index++)
{
var job = new HeavyCalculationJob(index, index);
list.Add(job);
}

jobScheduler.Flush(handles.AsSpan());
jobScheduler.Wait(handles.AsSpan());
Console.WriteLine($"{sindex} done");
jobScheduler.Dispose();
Parallel.ForEach(list, job => job.Execute());
}

var time = timer.ElapsedMilliseconds;
Console.WriteLine($"Parallel foreach Time: {time}ms");
}

private static void BenchD()
{
var timer = Stopwatch.StartNew();
for (var sindex = 0; sindex < 100; sindex++)
{
Parallel.For(0, 20000, i =>
{
var job = new HeavyCalculationJob(i, i);
job.Execute();
});
}

var time = timer.ElapsedMilliseconds;
Console.WriteLine($"Parallel for Time: {time}ms");
}

private static void Main(string[] args)
{
var config = DefaultConfig.Instance.AddJob(Job.Default
.WithWarmupCount(2)
.WithMinIterationCount(10)
.WithIterationCount(20)
.WithMaxIterationCount(30)
// .WithAffinity(65535)//To not freeze my pc
);
config = config.WithOptions(ConfigOptions.DisableOptimizationsValidator);
BenchmarkRunner.Run<JobSchedulerBenchmark>(config);
return;
// var jb = new JobSchedulerBenchmark();
// jb.Setup();
// jb.Jobs = 512;
// jb.BenchmarkJobSchedulerNoAlloc();
// jb.Cleanup();
// return;
for (int i = 0; i < 10; i++)
{
BenchB();
BenchC();
BenchD();
}
//using var jobScheduler = new JobScheduler();

// Spawn massive jobs and wait for finish
Expand Down
23 changes: 19 additions & 4 deletions JobScheduler.Benchmarks/JobSchedulerBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CommunityToolkit.HighPerformance;
using Arch.Benchmarks;
using CommunityToolkit.HighPerformance;
using Schedulers.Utils;

namespace Schedulers.Benchmarks;
Expand All @@ -8,6 +9,7 @@ public class CalculationJob : IJob
private readonly int _first;
private readonly int _second;
public static volatile int Result;
public static int a;

public CalculationJob(int first, int second)
{
Expand All @@ -18,6 +20,7 @@ public CalculationJob(int first, int second)
public void Execute()
{
Result = _first + _second;
Interlocked.Increment(ref a);
}
}

Expand All @@ -29,7 +32,7 @@ public class JobSchedulerBenchmark

private static volatile int result = 0;

[Params(1, 32, 64, 128, 256, 512)] public int Jobs;
[Params(20000, 1000, 50, 10)] public int Jobs;

[IterationSetup]
public void Setup()
Expand All @@ -50,7 +53,7 @@ public void BenchmarkJobScheduler()
{
for (var index = 0; index < Jobs; index++)
{
var job = new CalculationJob(index, index);
var job = new HeavyCalculationJob(index, index);
var handle = _jobScheduler.Schedule(job);
_jobHandles.Add(handle);
}
Expand All @@ -64,8 +67,20 @@ public void BenchmarkParallelFor()
{
Parallel.For(0, Jobs, i =>
{
var job = new CalculationJob(i, i);
var job = new HeavyCalculationJob(i, i);
job.Execute();
});
}

[Benchmark]
public void BenchmarkJobSchedulerNoAlloc()
{
var generation = _jobScheduler.GetNewGeneration();
for (var index = 0; index < Jobs; index++)
{
var job = new HeavyCalculationJob(index, index);
_jobScheduler.ScheduleAndFlushPooledJobWithGeneration(job, generation);
}
_jobScheduler.AwaitForGeneration(generation);
}
}
44 changes: 30 additions & 14 deletions JobScheduler/JobHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,14 @@ namespace Schedulers;
/// is used to control and await a scheduled <see cref="IJob"/>.
/// <remarks>Size is exactly 64 bytes to fit perfectly into one default sized cacheline to reduce false sharing and be more efficient.</remarks>
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 64)]
public class JobHandle
{
internal readonly IJob _job;

internal readonly JobHandle _parent;
internal IJob _job;
internal JobHandle? _parent;
private List<JobHandle>? _dependencies;
internal int _unfinishedJobs;

internal readonly List<JobHandle> _dependencies;

private long _padding1;
private long _padding2;
private short _padding3;
private short _padding4;

internal int index = -1;//-1 for non pooled and 0 or higher for pooled
public int generation = 0;
/// <summary>
/// Creates a new <see cref="JobHandle"/>.
/// </summary>
Expand All @@ -32,7 +25,30 @@ public JobHandle(IJob job)
_job = job;
_parent = null;
_unfinishedJobs = 1;
_dependencies = [];
_dependencies = null;
}

public JobHandle(int index)
{
this.index = index;
}

public void ReinitializeWithJob(IJob job)
{
_job = job;
_parent = null;
_unfinishedJobs = 1;
_dependencies = null;
}

public bool HasDependencies()
{
return _dependencies is { Count: > 0 };
}

public List<JobHandle> GetDependencies()
{
return _dependencies ??= [];
}

/// <summary>
Expand All @@ -45,6 +61,6 @@ public JobHandle(IJob job, JobHandle parent)
_job = job;
_parent = parent;
_unfinishedJobs = 1;
_dependencies = [];
_dependencies = null;
}
}
Loading

0 comments on commit 4281a63

Please sign in to comment.