feat(jobs): add IJobScheduler interface and job scheduling improvements\n\nIntroduce IJobScheduler interface and enhance JobScheduler, WorkerThread, JobInfo and related collections. Add ConcurrentSlotMap tests and codegen generator updates.\n\nSee changed files for details.

This commit is contained in:
2026-04-17 16:08:20 +09:00
parent 123aa69a35
commit ebee3bb7fb
12 changed files with 402 additions and 304 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
.code-review-graph/
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs

View File

@@ -1,2 +1,5 @@
global using unsafe JobExecutionFunc = delegate*<void*, ref Misaki.HighPerformance.Jobs.JobRanges, ref int, ref readonly Misaki.HighPerformance.Jobs.JobExecutionContext, bool>; global using unsafe JobExecutionFunc = delegate*<void*, ref Misaki.HighPerformance.Jobs.JobRanges, ref int, ref readonly Misaki.HighPerformance.Jobs.JobExecutionContext, bool>;
#if MHP_SUPPORT_MANAGED_JOB
global using unsafe ManagedJobExecutionFunc = delegate*<object, ref Misaki.HighPerformance.Jobs.JobRanges, ref int, ref readonly Misaki.HighPerformance.Jobs.JobExecutionContext, bool>;
#endif

View File

@@ -0,0 +1,203 @@
namespace Misaki.HighPerformance.Jobs;
public interface IJobScheduler
{
/// <summary>
/// Gets the number of worker threads managed by the job scheduler.
/// </summary>
int WorkerCount
{
get;
}
/// <summary>
/// Schedules a single job for execution on a specified thread, with an optional dependency on another job.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <param name="dependency">A <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.
/// Use <see cref="JobHandle.Invalid"/> if there are no dependencies.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobHandle dependency)
where T : unmanaged, IJob;
/// <summary>
/// Schedules a single job for execution on a specified thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle Schedule<T>(ref readonly T job, int threadIndex)
where T : unmanaged, IJob;
/// <summary>
/// Schedules a single job for execution on any thread, with an optional dependency on another job.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle Schedule<T>(ref readonly T job, JobHandle dependency)
where T : unmanaged, IJob;
/// <summary>
/// Schedules a single job for execution on any thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle Schedule<T>(ref readonly T job)
where T : unmanaged, IJob;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <param name="dependency">A <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.
/// Use <see cref="JobHandle.Invalid"/> if there are no dependencies.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency)
where T : unmanaged, IJobParallelFor;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on a specified thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex)
where T : unmanaged, IJobParallelFor;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread, with an optional dependency on another job..
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, JobHandle dependency)
where T : unmanaged, IJobParallelFor;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize)
where T : unmanaged, IJobParallelFor;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <param name="dependency">A <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.
/// Use <see cref="JobHandle.Invalid"/> if there are no dependencies.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency)
where T : unmanaged, IJobParallel;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on a specified thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex)
where T : unmanaged, IJobParallel;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread, with an optional dependency on another job..
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, JobHandle dependency)
where T : unmanaged, IJobParallel;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize)
where T : unmanaged, IJobParallel;
/// <summary>
/// Combines multiple job dependencies into a single <see cref="JobHandle"/>.
/// </summary>
/// <param name="dependencies">A collection of <see cref="JobHandle"/> instances representing the dependencies to combine.</param>
/// <returns>A <see cref="JobHandle"/> that represents the combined dependencies. The returned handle can be used to ensure
/// that all specified dependencies are completed before proceeding.</returns>
JobHandle CombineDependencies(params ReadOnlySpan<JobHandle> dependencies);
/// <summary>
/// Retrieves the current status of a job identified by the specified handle.
/// </summary>
/// <param name="handle">The handle representing the job whose status is to be retrieved. The handle must be valid.</param>
/// <returns>The current status of the job as a <see cref="JobState"/> value.
/// Returns <see cref="JobState.Invalid"/> if the handle is invalid or the job does not exist.</returns>
JobState GetJobStatus(JobHandle handle);
/// <summary>
/// Blocks the calling thread until the specified job is completed.
/// </summary>
/// <param name="handle">The handle of the job to wait for.</param>
void Wait(JobHandle handle);
/// <summary>
/// Blocks the calling thread until all specified job handles have completed.
/// </summary>
/// <remarks>
/// The collection handles will be reordered in-place to move completed handles to the front.
/// </remarks>
/// <param name="handles">A collection of job handles to wait for.</param>
void WaitAll(params Span<JobHandle> handles);
/// <summary>
/// Waits until any of the specified job handles has completed and returns the first completed handle.
/// </summary>
/// <param name="handles">A read-only span containing the job handles to monitor for completion.</param>
/// <returns>The first job handle from the provided collection that has completed.</returns>
JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles);
}

View File

@@ -1,4 +1,10 @@
namespace Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Jobs;
/// <summary> /// <summary>
/// The state of a job in its lifecycle. /// The state of a job in its lifecycle.
@@ -27,13 +33,62 @@ public enum JobState
Completed = 3 Completed = 3
} }
internal enum HeapType
{
Native,
Managed,
}
internal unsafe struct JobInfo internal unsafe struct JobInfo
{ {
public ref struct DependentIterator
{
private readonly ref JobInfo _jobInfo;
private int _index;
public DependentIterator(ref JobInfo jobInfo)
{
_jobInfo = ref jobInfo;
_index = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_index++;
return _index < _jobInfo.dependentCount;
}
public JobHandle Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (_index < MAX_DEPENDENTS)
{
return new JobHandle(_jobInfo.dependentsID[_index], _jobInfo.dependentsGeneration[_index]);
}
else
{
return _jobInfo.additionalDependents[_index - MAX_DEPENDENTS];
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
_index = -1;
}
}
public const int MAX_DEPENDENTS = 8; public const int MAX_DEPENDENTS = 8;
// 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
public UnsafeList<JobHandle> additionalDependents;
public int dependentCount; public int dependentCount;
public JobRanges jobRanges; public JobRanges jobRanges;
@@ -41,11 +96,18 @@ internal unsafe struct JobInfo
public void* pJobData; public void* pJobData;
public JobExecutionFunc pExecutionFunc; public JobExecutionFunc pExecutionFunc;
public JobState state; public int state;
public int remainingBatches; public int remainingBatches;
public int threadIndex; // The preferred thread index to run this job on, -1 means any thread public int threadIndex; // The preferred thread index to run this job on, -1 means any thread
public int dependencyCount; // Numbers of jobs that this job depends on, when it reaches 0, the job can be executed public int dependencyCount; // Numbers of jobs that this job depends on, when it reaches 0, the job can be executed
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[UnscopedRef]
public DependentIterator GetDependentIterator()
{
return new DependentIterator(ref this);
}
} }
internal struct JobRanges internal struct JobRanges
@@ -61,3 +123,48 @@ internal struct JobRanges
currentIndex = 0, currentIndex = 0,
}; };
} }
internal static class JobUtility
{
// Lock-Free constants: State mask (low 16 bits) and RC unit (1 << 16)
public const int STATE_MASK = 0xFFFF;
public const int RC_ONE = 0x10000;
public const int JOBSTATE_INVALID = (int)JobState.Invalid & STATE_MASK;
public const int JOBSTATE_CREATED = (int)JobState.Created & STATE_MASK;
public const int JOBSTATE_SCHEDULED = (int)JobState.Scheduled & STATE_MASK;
public const int JOBSTATE_RUNNING = (int)JobState.Running & STATE_MASK;
public const int JOBSTATE_COMPLETED = (int)JobState.Completed & STATE_MASK;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JobState ReadState(ref JobInfo jobInfo)
{
var stateVal = Volatile.Read(ref jobInfo.state);
return (JobState)(stateVal & STATE_MASK);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadStateValue(ref JobInfo jobInfo)
{
var stateVal = Volatile.Read(ref jobInfo.state);
return stateVal & STATE_MASK;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetStateValue(JobState state)
{
return (int)state & STATE_MASK;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JobState GetState(int value)
{
return (JobState)(value & STATE_MASK);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReleaseRC(ref int jobState)
{
Interlocked.Add(ref jobState, -RC_ONE);
}
}

View File

@@ -1,212 +1,14 @@
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Jobs; namespace Misaki.HighPerformance.Jobs;
public interface IJobScheduler
{
/// <summary>
/// Gets the number of worker threads managed by the job scheduler.
/// </summary>
int WorkerCount
{
get;
}
/// <summary>
/// Schedules a single job for execution on a specified thread, with an optional dependency on another job.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <param name="dependency">A <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.
/// Use <see cref="JobHandle.Invalid"/> if there are no dependencies.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobHandle dependency)
where T : unmanaged, IJob;
/// <summary>
/// Schedules a single job for execution on a specified thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle Schedule<T>(ref readonly T job, int threadIndex)
where T : unmanaged, IJob;
/// <summary>
/// Schedules a single job for execution on any thread, with an optional dependency on another job.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle Schedule<T>(ref readonly T job, JobHandle dependency)
where T : unmanaged, IJob;
/// <summary>
/// Schedules a single job for execution on any thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle Schedule<T>(ref readonly T job)
where T : unmanaged, IJob;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <param name="dependency">A <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.
/// Use <see cref="JobHandle.Invalid"/> if there are no dependencies.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency)
where T : unmanaged, IJobParallelFor;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on a specified thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex)
where T : unmanaged, IJobParallelFor;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread, with an optional dependency on another job..
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, JobHandle dependency)
where T : unmanaged, IJobParallelFor;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize)
where T : unmanaged, IJobParallelFor;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <param name="dependency">A <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.
/// Use <see cref="JobHandle.Invalid"/> if there are no dependencies.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency)
where T : unmanaged, IJobParallel;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on a specified thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex)
where T : unmanaged, IJobParallel;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread, with an optional dependency on another job..
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, JobHandle dependency)
where T : unmanaged, IJobParallel;
/// <summary>
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread without dependency.
/// </summary>
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
/// <param name="batchSize">The number of iterations to include in each batch.</param>
/// <param name="threadIndex">The index of the thread that is preferred to execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize)
where T : unmanaged, IJobParallel;
/// <summary>
/// Combines multiple job dependencies into a single <see cref="JobHandle"/>.
/// </summary>
/// <param name="dependencies">A collection of <see cref="JobHandle"/> instances representing the dependencies to combine.</param>
/// <returns>A <see cref="JobHandle"/> that represents the combined dependencies. The returned handle can be used to ensure
/// that all specified dependencies are completed before proceeding.</returns>
JobHandle CombineDependencies(params ReadOnlySpan<JobHandle> dependencies);
/// <summary>
/// Retrieves the current status of a job identified by the specified handle.
/// </summary>
/// <param name="handle">The handle representing the job whose status is to be retrieved. The handle must be valid.</param>
/// <returns>The current status of the job as a <see cref="JobState"/> value.
/// Returns <see cref="JobState.Invalid"/> if the handle is invalid or the job does not exist.</returns>
JobState GetJobStatus(JobHandle handle);
/// <summary>
/// Blocks the calling thread until the specified job is completed.
/// </summary>
/// <param name="handle">The handle of the job to wait for.</param>
void Wait(JobHandle handle);
/// <summary>
/// Blocks the calling thread until all specified job handles have completed.
/// </summary>
/// <remarks>
/// The collection handles will be reordered in-place to move completed handles to the front.
/// </remarks>
/// <param name="handles">A collection of job handles to wait for.</param>
void WaitAll(params Span<JobHandle> handles);
/// <summary>
/// Waits until any of the specified job handles has completed and returns the first completed handle.
/// </summary>
/// <param name="handles">A read-only span containing the job handles to monitor for completion.</param>
/// <returns>The first job handle from the provided collection that has completed.</returns>
JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles);
}
/// <summary> /// <summary>
/// Provides a mechanism for scheduling and executing jobs across multiple worker threads. /// Provides a mechanism for scheduling and executing jobs across multiple worker threads.
/// </summary> /// </summary>
@@ -215,9 +17,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
// Don't sleep indefinitely because that causes our 1ms job to become 15ms. // Don't sleep indefinitely because that causes our 1ms job to become 15ms.
private const int _SLEEP_THRESHOLD = -1; private const int _SLEEP_THRESHOLD = -1;
// Lock-Free constants: State mask (low 16 bits) and RC unit (1 << 16) private FreeList _freeList;
private const int _STATE_MASK = 0xFFFF;
private const int _RC_ONE = 0x10000;
private readonly ConcurrentSlotMap<JobInfo> _jobInfoPool; private readonly ConcurrentSlotMap<JobInfo> _jobInfoPool;
private readonly ConcurrentQueue<JobHandle> _jobQueue; private readonly ConcurrentQueue<JobHandle> _jobQueue;
@@ -236,11 +36,14 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
/// Initializes a new instance of the <see cref="JobScheduler"/> class with the specified number of worker threads. /// Initializes a new instance of the <see cref="JobScheduler"/> class with the specified number of worker threads.
/// </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>
public JobScheduler(int threadCount) /// <param name="priority">The priority of the worker threads.</param>
public JobScheduler(int threadCount, ThreadPriority priority = ThreadPriority.Normal)
{ {
var workerCount = Math.Max(1, threadCount); var workerCount = Math.Max(1, threadCount);
_jobInfoPool = new ConcurrentSlotMap<JobInfo>(); _freeList = new FreeList(MemoryUtility.AlignOf<IntPtr>(), maxConcurrencyLevel: threadCount);
_jobInfoPool = new ConcurrentSlotMap<JobInfo>(128);
_jobQueue = new ConcurrentQueue<JobHandle>(); _jobQueue = new ConcurrentQueue<JobHandle>();
_workSignal = new SemaphoreSlim(0); _workSignal = new SemaphoreSlim(0);
@@ -250,7 +53,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
for (var i = 0; i < workerCount; i++) for (var i = 0; i < workerCount; i++)
{ {
_workerThreads[i] = new WorkerThread(i, this); _workerThreads[i] = new WorkerThread(i, this, priority);
} }
foreach (var worker in _workerThreads) foreach (var worker in _workerThreads)
@@ -271,7 +74,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
if (exist && Volatile.Read(ref jobInfo.dependencyCount) == 0) if (exist && Volatile.Read(ref jobInfo.dependencyCount) == 0)
{ {
// Note: JobState.Created is 0, JobState.Scheduled is 1. We assume RC logic doesn't touch initial state (RC=0). // Note: JobState.Created is 0, JobState.Scheduled is 1. We assume RC logic doesn't touch initial state (RC=0).
if (Interlocked.CompareExchange(ref jobInfo.state, JobState.Scheduled, JobState.Created) != JobState.Created) if (Interlocked.CompareExchange(ref jobInfo.state, JobUtility.JOBSTATE_SCHEDULED, JobUtility.JOBSTATE_CREATED) != JobUtility.JOBSTATE_CREATED)
{ {
return; return;
} }
@@ -341,8 +144,8 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
while (true) while (true)
{ {
var stateVal = Volatile.Read(ref Unsafe.As<JobState, int>(ref depJobInfo.state)); var stateVal = Volatile.Read(ref depJobInfo.state);
var state = (JobState)(stateVal & _STATE_MASK); var state = JobUtility.GetState(stateVal);
if (state == JobState.Completed) if (state == JobState.Completed)
{ {
@@ -350,7 +153,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
} }
// Attempt to increment RC (Reader Count) // Attempt to increment RC (Reader Count)
if (Interlocked.CompareExchange(ref Unsafe.As<JobState, int>(ref depJobInfo.state), stateVal + _RC_ONE, stateVal) == stateVal) if (Interlocked.CompareExchange(ref depJobInfo.state, stateVal + JobUtility.RC_ONE, stateVal) == stateVal)
{ {
// RC acquired. We are safe from "Remove" and state change. // RC acquired. We are safe from "Remove" and state change.
var count = Interlocked.Increment(ref depJobInfo.dependentCount); var count = Interlocked.Increment(ref depJobInfo.dependentCount);
@@ -359,22 +162,21 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
// Safely write to the fixed buffer // Safely write to the fixed buffer
depJobInfo.dependentsID[count - 1] = id; depJobInfo.dependentsID[count - 1] = id;
depJobInfo.dependentsGeneration[count - 1] = generation; depJobInfo.dependentsGeneration[count - 1] = generation;
registered = true;
} }
else
{
if (!depJobInfo.additionalDependents.IsCreated)
{
depJobInfo.additionalDependents = new UnsafeList<JobHandle>(4, AllocationHandle.Persistent);
}
depJobInfo.additionalDependents.Add(handle);
}
registered = true;
// Release RC // Release RC
Interlocked.Add(ref Unsafe.As<JobState, int>(ref depJobInfo.state), -_RC_ONE); Interlocked.Add(ref depJobInfo.state, -JobUtility.RC_ONE);
if (!registered)
{
// Failed to register because MAX_DEPENDENTS reached.
// Backtrack the counter increment.
Interlocked.Decrement(ref depJobInfo.dependentCount);
// Cleanup and fail
NativeMemory.Free(jobInfo.pJobData);
return JobHandle.Invalid;
}
break; break;
} }
@@ -449,10 +251,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
internal void MarkJobComplete(JobHandle handle) internal void MarkJobComplete(JobHandle handle)
{ {
if (!handle.IsValid) Debug.Assert(handle.IsValid);
{
return;
}
ref var info = ref _jobInfoPool.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); ref var info = ref _jobInfoPool.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist) if (!exist)
@@ -467,31 +266,23 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
var spin = new SpinWait(); var spin = new SpinWait();
while (true) while (true)
{ {
var stateVal = Volatile.Read(ref Unsafe.As<JobState, int>(ref info.state)); var stateVal = Volatile.Read(ref info.state);
var state = (JobState)(stateVal & _STATE_MASK); var state = JobUtility.GetState(stateVal);
if (state == JobState.Completed) if (state == JobState.Completed)
{ {
return; return;
} }
//if (state != JobState.Running) // Preserve upper bits (RC) and set state to Completed. This blocks new Readers.
//{ var newState = (stateVal & ~JobUtility.STATE_MASK) | (int)JobState.Completed;
// // If in valid state (e.g. Scheduled?), we still assume we can complete it. if (Interlocked.CompareExchange(ref info.state, newState, stateVal) == stateVal)
// // Usually it should be Running.
//}
// Construct new value: State=Completed, preserve RC (temporarily) or strictly replace only low bits?
// We set low bits to Completed. High bits (RC) remain.
var newState = (stateVal & ~_STATE_MASK) | (int)JobState.Completed;
if (Interlocked.CompareExchange(ref Unsafe.As<JobState, int>(ref info.state), newState, stateVal) == stateVal)
{ {
// Successfully set State to Completed. New readers will see Completed and back off. // Successfully set State to Completed. New readers will see Completed and back off.
// Now we must wait for existing readers to finish (RC to become 0). // Now we must wait for existing readers to finish (RC to become 0).
while (true) while (true)
{ {
var current = Volatile.Read(ref Unsafe.As<JobState, int>(ref info.state)); var current = Volatile.Read(ref info.state);
if (((uint)current >> 16) == 0) if (((uint)current >> 16) == 0)
{ {
break; // RC is 0. Safe to proceed. break; // RC is 0. Safe to proceed.
@@ -505,20 +296,10 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
spin.SpinOnce(-1); spin.SpinOnce(-1);
} }
// We now have exclusive access to dependentsID (no new readers, old readers finished). var it = info.GetDependentIterator();
var dependentCount = info.dependentCount; while (it.MoveNext())
var dependentsToNotify = stackalloc JobHandle[dependentCount];
for (var i = 0; i < dependentCount; i++)
{ {
dependentsToNotify[i] = new JobHandle(info.dependentsID[i], info.dependentsGeneration[i]); var depHandle = it.Current;
}
NativeMemory.Free(info.pJobData);
_jobInfoPool.Remove(handle.ID, handle.Generation);
for (var i = 0; i < dependentCount; i++)
{
var depHandle = dependentsToNotify[i];
ref var depJobInfo = ref _jobInfoPool.GetElementReferenceAt(depHandle.ID, depHandle.Generation, out var depExist); ref var depJobInfo = ref _jobInfoPool.GetElementReferenceAt(depHandle.ID, depHandle.Generation, out var depExist);
if (depExist && Interlocked.Decrement(ref depJobInfo.dependencyCount) == 0) if (depExist && Interlocked.Decrement(ref depJobInfo.dependencyCount) == 0)
@@ -526,12 +307,15 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
EnqueueJobIfReady(depHandle); EnqueueJobIfReady(depHandle);
} }
} }
_freeList.Free(info.pJobData);
_jobInfoPool.Remove(handle.ID, handle.Generation);
} }
public JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobHandle dependency) public JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobHandle dependency)
where T : unmanaged, IJob where T : unmanaged, IJob
{ {
var pJobData = NativeMemory.Alloc((nuint)sizeof(T)); var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
if (pJobData == null) if (pJobData == null)
{ {
return JobHandle.Invalid; return JobHandle.Invalid;
@@ -568,7 +352,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency) public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency)
where T : unmanaged, IJobParallelFor where T : unmanaged, IJobParallelFor
{ {
var pJobData = NativeMemory.Alloc((nuint)sizeof(T)); var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
if (pJobData == null) if (pJobData == null)
{ {
return JobHandle.Invalid; return JobHandle.Invalid;
@@ -587,7 +371,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
remainingBatches = totalBatches, remainingBatches = totalBatches,
threadIndex = threadIndex, threadIndex = threadIndex,
jobRanges = new() jobRanges = new JobRanges()
{ {
currentIndex = 0, currentIndex = 0,
batchSize = optimalBatchSize, batchSize = optimalBatchSize,
@@ -613,7 +397,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency) public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency)
where T : unmanaged, IJobParallel where T : unmanaged, IJobParallel
{ {
var pJobData = NativeMemory.Alloc((nuint)sizeof(T)); var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
if (pJobData == null) if (pJobData == null)
{ {
return JobHandle.Invalid; return JobHandle.Invalid;
@@ -632,7 +416,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
remainingBatches = totalBatches, remainingBatches = totalBatches,
threadIndex = threadIndex, threadIndex = threadIndex,
jobRanges = new() jobRanges = new JobRanges()
{ {
currentIndex = 0, currentIndex = 0,
batchSize = optimalBatchSize, batchSize = optimalBatchSize,
@@ -685,7 +469,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
} }
// Mask out the Reader Count (upper 16 bits) to return the actual State // Mask out the Reader Count (upper 16 bits) to return the actual State
return (JobState)(Volatile.Read(ref Unsafe.As<JobState, int>(ref jobInfo.state)) & _STATE_MASK); return JobUtility.GetState(Volatile.Read(ref jobInfo.state));
} }
public void Wait(JobHandle handle) public void Wait(JobHandle handle)
@@ -701,14 +485,14 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
var spin = new SpinWait(); var spin = new SpinWait();
while (true) while (true)
{ {
ref readonly var jobInfo = ref _jobInfoPool.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); ref var jobInfo = ref _jobInfoPool.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist) if (!exist)
{ {
return; return;
} }
// Mask out RC // Mask out RC
if ((jobInfo.state & (JobState)_STATE_MASK) == JobState.Completed) if (JobUtility.ReadState(ref jobInfo) == JobState.Completed)
{ {
return; return;
} }

View File

@@ -6,11 +6,13 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<AssemblyVersion>1.5.9</AssemblyVersion> <AssemblyVersion>1.6.0</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>
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl> <RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>contentFiles</ContentTargetFolders>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -22,6 +24,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" /> <ProjectReference Include="../Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj" />
<ProjectReference Include="../Misaki.HighPerformance/Misaki.HighPerformance.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="**/*.cs" Exclude="obj/**;bin/**">
<Pack>true</Pack>
<PackagePath>contentFiles/cs/any/Misaki.HighPerformance.Jobs/</PackagePath>
<PackageCopyToOutput>false</PackageCopyToOutput>
<BuildAction>Compile</BuildAction>
</Content>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -15,7 +15,7 @@ internal class WorkerThread : IDisposable
internal ConcurrentQueue<JobHandle> LocalQueue => _localQueue; internal ConcurrentQueue<JobHandle> LocalQueue => _localQueue;
public WorkerThread(int index, JobScheduler scheduler) public WorkerThread(int index, JobScheduler scheduler, ThreadPriority priority)
{ {
_index = index; _index = index;
_localQueue = new(); _localQueue = new();
@@ -25,7 +25,8 @@ internal class WorkerThread : IDisposable
_thread = new Thread(WorkLoop) _thread = new Thread(WorkLoop)
{ {
IsBackground = true, IsBackground = true,
Name = $"WorkerThread-{index}" Name = $"WorkerThread-{index}",
Priority = priority
}; };
} }
@@ -97,8 +98,8 @@ internal class WorkerThread : IDisposable
ref var jobInfo = ref _scheduler.GetJobInfoReference(handle, out var exist); ref var jobInfo = ref _scheduler.GetJobInfoReference(handle, out var exist);
if (exist) if (exist)
{ {
var priorState = Interlocked.CompareExchange(ref jobInfo.state, JobState.Running, JobState.Scheduled); var priorState = Interlocked.CompareExchange(ref jobInfo.state, JobUtility.JOBSTATE_RUNNING, JobUtility.JOBSTATE_SCHEDULED);
if (priorState != JobState.Scheduled && priorState != JobState.Running) if (priorState != JobUtility.JOBSTATE_SCHEDULED && priorState != JobUtility.JOBSTATE_RUNNING)
{ {
continue; continue;
} }

View File

@@ -68,20 +68,9 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
get get
{{ {{
RangeCheck(index);
return ref (({typeInfo.ComponentTypeFullName}*)global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref this))[index]; return ref (({typeInfo.ComponentTypeFullName}*)global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref this))[index];
}} }}
}}"); }}");
sourceBuilder.AppendLine(@$"
[global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")]
private void RangeCheck(int index)
{{
if (index < 0 || index >= {typeInfo.Column})
{{
throw new global::System.ArgumentOutOfRangeException(nameof(index), $""Index {{index}} is out of range of '{typeInfo.TypeName}'"");
}}
}}");
} }
private void GenerateUnitMatrix() private void GenerateUnitMatrix()

View File

@@ -127,20 +127,9 @@ namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
{INLINE_METHOD_ATTRIBUTE} {INLINE_METHOD_ATTRIBUTE}
get get
{{ {{
RangeCheck(index);
return ref global::System.Runtime.CompilerServices.Unsafe.Add(ref {s_vectorComponents[0]}, index); return ref global::System.Runtime.CompilerServices.Unsafe.Add(ref {s_vectorComponents[0]}, index);
}} }}
}}"); }}");
sourceBuilder.AppendLine(@$"
[global::System.Diagnostics.Conditional(""ENABLE_COLLECTION_CHECKS"")]
private void RangeCheck(int index)
{{
if (index < 0 || index >= {typeInfo.Row})
{{
throw new global::System.ArgumentOutOfRangeException(nameof(index), $""Index {{index}} is out of range of '{typeInfo.TypeName}'"");
}}
}}");
} }
private static List<List<int>> GetPartitions(int target) private static List<List<int>> GetPartitions(int target)

View File

@@ -3,10 +3,19 @@ using Misaki.HighPerformance.LowLevel.Collections;
//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
{
fixed (int* ptr = span)
{
Console.WriteLine((IntPtr)ptr);
}
}

View File

@@ -18,7 +18,7 @@ public class TestConcurrentSlotMap
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
{ {
_slotMap = new ConcurrentSlotMap<int>(); _slotMap = new ConcurrentSlotMap<int>(16);
} }
[TestMethod] [TestMethod]

View File

@@ -88,7 +88,7 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public ConcurrentSlotMap(int initialCapacity = 256) public ConcurrentSlotMap(int initialCapacity)
{ {
_count = 0; _count = 0;
_nextSlotIndex = 0; _nextSlotIndex = 0;