Refactor JobExecutionContext, add scheduler state support
- Refactored JobExecutionContext to use init-only properties and added State and SelfHandle for richer context. - Updated JobInfo field layout for clarity and memory alignment. - JobScheduler now accepts and exposes an optional state object, passed to each job context. - Improved memory management by using _freeList for allocations and disposal. - WorkerThread and benchmarks updated to use new JobExecutionContext pattern. - Bumped version to 1.6.1 and performed minor code cleanup.
This commit is contained in:
@@ -4,17 +4,21 @@ public readonly ref struct JobExecutionContext
|
|||||||
{
|
{
|
||||||
public int ThreadIndex
|
public int ThreadIndex
|
||||||
{
|
{
|
||||||
get;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IJobScheduler JobScheduler
|
public IJobScheduler JobScheduler
|
||||||
{
|
{
|
||||||
get;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JobExecutionContext(int threadIndex, IJobScheduler jobScheduler)
|
public object? State
|
||||||
{
|
{
|
||||||
ThreadIndex = threadIndex;
|
get; init;
|
||||||
JobScheduler = jobScheduler;
|
}
|
||||||
|
|
||||||
|
public JobHandle SelfHandle
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,6 +84,9 @@ internal unsafe struct JobInfo
|
|||||||
|
|
||||||
public const int MAX_DEPENDENTS = 8;
|
public const int MAX_DEPENDENTS = 8;
|
||||||
|
|
||||||
|
public void* pJobData;
|
||||||
|
public JobExecutionFunc pExecutionFunc;
|
||||||
|
|
||||||
// The list of jobs that are waiting for THIS job to complete.
|
// The list of jobs that are waiting for THIS job to complete.
|
||||||
public fixed int dependentsID[MAX_DEPENDENTS]; // The actual list of IDs
|
public fixed int dependentsID[MAX_DEPENDENTS]; // The actual list of IDs
|
||||||
public fixed int dependentsGeneration[MAX_DEPENDENTS]; // The actual list of generations
|
public fixed int dependentsGeneration[MAX_DEPENDENTS]; // The actual list of generations
|
||||||
@@ -93,9 +96,6 @@ internal unsafe struct JobInfo
|
|||||||
|
|
||||||
public JobRanges jobRanges;
|
public JobRanges jobRanges;
|
||||||
|
|
||||||
public void* pJobData;
|
|
||||||
public JobExecutionFunc pExecutionFunc;
|
|
||||||
|
|
||||||
public int state;
|
public int state;
|
||||||
public int remainingBatches;
|
public int remainingBatches;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Misaki.HighPerformance.LowLevel.Utilities;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.Jobs;
|
namespace Misaki.HighPerformance.Jobs;
|
||||||
|
|
||||||
@@ -26,9 +25,12 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
private readonly SemaphoreSlim _workSignal;
|
private readonly SemaphoreSlim _workSignal;
|
||||||
private readonly CancellationTokenSource _cts;
|
private readonly CancellationTokenSource _cts;
|
||||||
|
|
||||||
|
private readonly object? _state;
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
internal bool IsCancellationRequested => _cts.IsCancellationRequested;
|
internal bool IsCancellationRequested => _cts.IsCancellationRequested;
|
||||||
|
internal object? State => _state;
|
||||||
|
|
||||||
public int WorkerCount => _workerThreads.Length;
|
public int WorkerCount => _workerThreads.Length;
|
||||||
|
|
||||||
@@ -37,7 +39,8 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="threadCount">The number of worker threads to create. If less than 1, at least one thread will be created.</param>
|
/// <param name="threadCount">The number of worker threads to create. If less than 1, at least one thread will be created.</param>
|
||||||
/// <param name="priority">The priority of the worker threads.</param>
|
/// <param name="priority">The priority of the worker threads.</param>
|
||||||
public JobScheduler(int threadCount, ThreadPriority priority = ThreadPriority.Normal)
|
/// <param name="state">The state object for the job scheduler.</param>
|
||||||
|
public JobScheduler(int threadCount, ThreadPriority priority = ThreadPriority.Normal, object? state = null)
|
||||||
{
|
{
|
||||||
var workerCount = Math.Max(1, threadCount);
|
var workerCount = Math.Max(1, threadCount);
|
||||||
|
|
||||||
@@ -49,6 +52,8 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
_workSignal = new SemaphoreSlim(0);
|
_workSignal = new SemaphoreSlim(0);
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
_state = state;
|
||||||
|
|
||||||
_workerThreads = new WorkerThread[workerCount];
|
_workerThreads = new WorkerThread[workerCount];
|
||||||
|
|
||||||
for (var i = 0; i < workerCount; i++)
|
for (var i = 0; i < workerCount; i++)
|
||||||
@@ -308,6 +313,8 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info.additionalDependents.Dispose();
|
||||||
|
|
||||||
_freeList.Free(info.pJobData);
|
_freeList.Free(info.pJobData);
|
||||||
_jobInfoPool.Remove(handle.ID, handle.Generation);
|
_jobInfoPool.Remove(handle.ID, handle.Generation);
|
||||||
}
|
}
|
||||||
@@ -320,7 +327,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
{
|
{
|
||||||
return JobHandle.Invalid;
|
return JobHandle.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
*(T*)pJobData = job;
|
*(T*)pJobData = job;
|
||||||
|
|
||||||
var jobInfo = new JobInfo
|
var jobInfo = new JobInfo
|
||||||
@@ -573,10 +580,11 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
{
|
{
|
||||||
if (info.pJobData != null)
|
if (info.pJobData != null)
|
||||||
{
|
{
|
||||||
NativeMemory.Free(info.pJobData);
|
_freeList.Free(info.pJobData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_freeList.Dispose();
|
||||||
_workSignal.Dispose();
|
_workSignal.Dispose();
|
||||||
_cts.Dispose();
|
_cts.Dispose();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
<AssemblyVersion>1.6.0</AssemblyVersion>
|
<AssemblyVersion>1.6.1</AssemblyVersion>
|
||||||
<Version>$(AssemblyVersion)</Version>
|
<Version>$(AssemblyVersion)</Version>
|
||||||
<Authors>Misaki</Authors>
|
<Authors>Misaki</Authors>
|
||||||
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
||||||
|
|||||||
@@ -106,7 +106,14 @@ internal class WorkerThread : IDisposable
|
|||||||
|
|
||||||
if (jobInfo.pExecutionFunc != null)
|
if (jobInfo.pExecutionFunc != null)
|
||||||
{
|
{
|
||||||
var ctx = new JobExecutionContext(_index, _scheduler);
|
var ctx = new JobExecutionContext
|
||||||
|
{
|
||||||
|
ThreadIndex = _index,
|
||||||
|
JobScheduler = _scheduler,
|
||||||
|
State = _scheduler.State,
|
||||||
|
SelfHandle = handle,
|
||||||
|
};
|
||||||
|
|
||||||
if (!jobInfo.pExecutionFunc(jobInfo.pJobData, ref jobInfo.jobRanges, ref jobInfo.remainingBatches, in ctx))
|
if (!jobInfo.pExecutionFunc(jobInfo.pJobData, ref jobInfo.jobRanges, ref jobInfo.remainingBatches, in ctx))
|
||||||
{
|
{
|
||||||
// If the job returns false, it means it we are not the last worker to process this job, so we should not mark it as complete yet.
|
// If the job returns false, it means it we are not the last worker to process this job, so we should not mark it as complete yet.
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ public unsafe class SPMDBenchmark
|
|||||||
height = _SIZE,
|
height = _SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
var ctx = new JobExecutionContext(-1, _scheduler);
|
job.Run(_SIZE * _SIZE, default);
|
||||||
job.Run(_SIZE * _SIZE, in ctx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,136 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
//BenchmarkRunner.Run<SPMDBenchmark>();
|
//BenchmarkRunner.Run<SPMDBenchmark>();
|
||||||
|
|
||||||
//AllocationManager.Initialize(AllocationManagerInitOpts.Default);
|
AllocationManager.Initialize(AllocationManagerInitOpts.Default);
|
||||||
//var set = new UnsafeBitSet(100, AllocationHandle.Persistent, AllocationOption.Clear);
|
var set = new UnsafeBitSet(100, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||||
//set.SetBit(0);
|
set.SetBit(0);
|
||||||
//Console.WriteLine(set.NextSetBit(0));
|
Console.WriteLine(set.NextSetBit(0));
|
||||||
|
|
||||||
//set.Dispose();
|
set.Dispose();
|
||||||
//AllocationManager.Dispose();
|
AllocationManager.Dispose();
|
||||||
|
|
||||||
var span = Span<int>.Empty;
|
//unsafe
|
||||||
unsafe
|
//{
|
||||||
{
|
// var testC = new TestC();
|
||||||
fixed (int* ptr = span)
|
// testC.BindToVtables();
|
||||||
{
|
// testC.MyValue = 42;
|
||||||
Console.WriteLine((IntPtr)ptr);
|
|
||||||
}
|
// // 1. Casting to IB (Direct cast, offset 0)
|
||||||
}
|
// var pB = (IB.Interface*)&testC;
|
||||||
|
// pB->MethodA();
|
||||||
|
// pB->MethodB();
|
||||||
|
|
||||||
|
// // 2. Casting to IC (Pointer adjustment needed!)
|
||||||
|
// // We must offset the pointer by the size of one void* so it points to lpVtbl_IC
|
||||||
|
// var pC = (IC.Interface*)((byte*)&testC + sizeof(void*));
|
||||||
|
// pC->MethodC();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public interface IA
|
||||||
|
//{
|
||||||
|
// [StructLayout(LayoutKind.Sequential)]
|
||||||
|
// unsafe struct Interface
|
||||||
|
// {
|
||||||
|
// public void** lpVtbl;
|
||||||
|
// public void MethodA() => ((delegate*<Interface*, void>)lpVtbl[0])((Interface*)Unsafe.AsPointer(ref this));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public interface IB : IA
|
||||||
|
//{
|
||||||
|
// [StructLayout(LayoutKind.Sequential)]
|
||||||
|
// new unsafe struct Interface
|
||||||
|
// {
|
||||||
|
// public void** lpVtbl;
|
||||||
|
// // IA Methods
|
||||||
|
// public void MethodA() => ((delegate*<Interface*, void>)lpVtbl[0])((Interface*)Unsafe.AsPointer(ref this));
|
||||||
|
// // IB Methods
|
||||||
|
// public void MethodB() => ((delegate*<Interface*, void>)lpVtbl[1])((Interface*)Unsafe.AsPointer(ref this));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public interface IC
|
||||||
|
//{
|
||||||
|
// [StructLayout(LayoutKind.Sequential)]
|
||||||
|
// unsafe struct Interface
|
||||||
|
// {
|
||||||
|
// public void** lpVtbl;
|
||||||
|
// public void MethodC() => ((delegate*<Interface*, void>)lpVtbl[0])((Interface*)Unsafe.AsPointer(ref this));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//[StructLayout(LayoutKind.Sequential)]
|
||||||
|
//public unsafe partial struct TestC
|
||||||
|
//{
|
||||||
|
// // Offset 0: Primary VTable (Covers IB and IA)
|
||||||
|
// public void** lpVtbl_IB;
|
||||||
|
|
||||||
|
// // Offset 8 (on 64-bit): Secondary VTable (Covers IC)
|
||||||
|
// public void** lpVtbl_IC;
|
||||||
|
|
||||||
|
// // Offset 16: Fields
|
||||||
|
// public int MyValue;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public unsafe partial struct TestC
|
||||||
|
//{
|
||||||
|
// public struct Vtbl_IB
|
||||||
|
// {
|
||||||
|
// public delegate*<IB.Interface*, void> MethodA;
|
||||||
|
// public delegate*<IB.Interface*, void> MethodB;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public struct Vtbl_IC
|
||||||
|
// {
|
||||||
|
// public delegate*<IC.Interface*, void> MethodC;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// [FixedAddressValueType]
|
||||||
|
// public static readonly Vtbl_IB Table_IB = new Vtbl_IB
|
||||||
|
// {
|
||||||
|
// MethodA = &Native_MethodA,
|
||||||
|
// MethodB = &Native_MethodB
|
||||||
|
// };
|
||||||
|
|
||||||
|
// [FixedAddressValueType]
|
||||||
|
// public static readonly Vtbl_IC Table_IC = new Vtbl_IC
|
||||||
|
// {
|
||||||
|
// MethodC = &Native_MethodC
|
||||||
|
// };
|
||||||
|
|
||||||
|
// public void BindToVtables()
|
||||||
|
// {
|
||||||
|
// fixed (Vtbl_IB* pIB = &Table_IB)
|
||||||
|
// lpVtbl_IB = (void**)pIB;
|
||||||
|
// fixed (Vtbl_IC* pIC = &Table_IC)
|
||||||
|
// lpVtbl_IC = (void**)pIC;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // --- IB & IA Implementations ---
|
||||||
|
// public static void Native_MethodA(IB.Interface* self)
|
||||||
|
// {
|
||||||
|
// // For IB/IA, no adjustment is needed because lpVtbl_IB is at offset 0
|
||||||
|
// var pThis = (TestC*)self;
|
||||||
|
// Console.WriteLine($"MethodA called. MyValue: {pThis->MyValue}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public static void Native_MethodB(IB.Interface* self)
|
||||||
|
// {
|
||||||
|
// var pThis = (TestC*)self;
|
||||||
|
// Console.WriteLine($"MethodB called. MyValue: {pThis->MyValue}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // --- IC Implementations ---
|
||||||
|
// public static void Native_MethodC(IC.Interface* self)
|
||||||
|
// {
|
||||||
|
// // WARNING: 'self' points to the lpVtbl_IC field, NOT the start of TestC!
|
||||||
|
// // We must shift the pointer backward by the size of lpVtbl_IB (sizeof(void*))
|
||||||
|
// // to reconstruct the original TestC pointer, otherwise we read garbage memory.
|
||||||
|
// var pThis = (TestC*)((byte*)self - sizeof(void*));
|
||||||
|
// Console.WriteLine($"MethodC called. MyValue: {pThis->MyValue}");
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -275,8 +275,7 @@ public unsafe class TestJobSystem
|
|||||||
height = size,
|
height = size,
|
||||||
};
|
};
|
||||||
|
|
||||||
var ctx = new JobExecutionContext(-1, s_jobScheduler);
|
vectorJob.Run(size * size, default);
|
||||||
vectorJob.Run(size * size, in ctx);
|
|
||||||
|
|
||||||
var spmdBuf = stackalloc float[size * size];
|
var spmdBuf = stackalloc float[size * size];
|
||||||
var ss = new Span<float>(spmdBuf, size * size);
|
var ss = new Span<float>(spmdBuf, size * size);
|
||||||
|
|||||||
Reference in New Issue
Block a user