Refactor job scheduler API, allocation, and benchmarks
- Removed IJobScheduler interface; merged logic into JobScheduler - Changed scheduling APIs to accept multiple dependencies (ReadOnlySpan) - Moved WaitItem classes to JobScheduler.cs and updated types - Updated JobExecutionContext to use JobScheduler and added docs - Renamed AllocationManagerInitOpts to AllocationManagerDesc (required props) - Added thread-safe TotalAllocatedMemory property to AllocationManager - Refactored Integer to Data in benchmarks; updated usage - Updated tests and improved documentation throughout
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@
|
|||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
.code-review-graph/
|
.code-review-graph/
|
||||||
|
.github/instructions/
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
|||||||
@@ -1,314 +0,0 @@
|
|||||||
namespace Misaki.HighPerformance.Jobs;
|
|
||||||
|
|
||||||
internal sealed class WaitItem : IThreadPoolWorkItem
|
|
||||||
{
|
|
||||||
private readonly IJobScheduler _scheduler;
|
|
||||||
private readonly JobHandle _jobHandle;
|
|
||||||
|
|
||||||
private readonly TaskCompletionSource _completionSource;
|
|
||||||
|
|
||||||
public Task Task => _completionSource.Task;
|
|
||||||
|
|
||||||
public WaitItem(IJobScheduler scheduler, JobHandle jobHandle, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_scheduler = scheduler;
|
|
||||||
_jobHandle = jobHandle;
|
|
||||||
_completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
cancellationToken.Register((cs, tk) => ((TaskCompletionSource)cs!).TrySetCanceled(tk), _completionSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute()
|
|
||||||
{
|
|
||||||
_scheduler.Wait(_jobHandle);
|
|
||||||
_completionSource.SetResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class WaitAllItem : IThreadPoolWorkItem
|
|
||||||
{
|
|
||||||
private readonly IJobScheduler _scheduler;
|
|
||||||
private readonly Memory<JobHandle> _jobHandles;
|
|
||||||
|
|
||||||
private readonly TaskCompletionSource _completionSource;
|
|
||||||
|
|
||||||
public Task Task => _completionSource.Task;
|
|
||||||
|
|
||||||
public WaitAllItem(IJobScheduler scheduler, Memory<JobHandle> jobHandles, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_scheduler = scheduler;
|
|
||||||
_jobHandles = jobHandles;
|
|
||||||
_completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
cancellationToken.Register((cs, tk) => ((TaskCompletionSource)cs!).TrySetCanceled(tk), _completionSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute()
|
|
||||||
{
|
|
||||||
_scheduler.WaitAll(_jobHandles.Span);
|
|
||||||
_completionSource.SetResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class WaitAnyItem : IThreadPoolWorkItem
|
|
||||||
{
|
|
||||||
private readonly IJobScheduler _scheduler;
|
|
||||||
private readonly ReadOnlyMemory<JobHandle> _jobHandles;
|
|
||||||
|
|
||||||
private readonly TaskCompletionSource<JobHandle> _completionSource;
|
|
||||||
|
|
||||||
public Task<JobHandle> Task => _completionSource.Task;
|
|
||||||
|
|
||||||
public WaitAnyItem(IJobScheduler scheduler, ReadOnlyMemory<JobHandle> jobHandles, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_scheduler = scheduler;
|
|
||||||
_jobHandles = jobHandles;
|
|
||||||
_completionSource = new TaskCompletionSource<JobHandle>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
|
|
||||||
cancellationToken.Register((cs, tk) => ((TaskCompletionSource)cs!).TrySetCanceled(tk), _completionSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute()
|
|
||||||
{
|
|
||||||
var completedHandle = _scheduler.WaitAny(_jobHandles.Span);
|
|
||||||
_completionSource.SetResult(completedHandle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
/// <param name="priority">The priority of 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, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
|
||||||
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, JobPriority priority = JobPriority.Normal)
|
|
||||||
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>
|
|
||||||
/// <param name="priority">The priority of 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, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
|
||||||
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>
|
|
||||||
/// <param name="priority">The priority of 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, JobPriority priority = JobPriority.Normal)
|
|
||||||
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>
|
|
||||||
/// <param name="priority">The priority of 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, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
|
||||||
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>
|
|
||||||
/// <param name="priority">The priority of 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, JobPriority priority = JobPriority.Normal)
|
|
||||||
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="dependency">The job that this job depends on.</param>
|
|
||||||
/// <param name="priority">The priority of 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, JobPriority priority = JobPriority.Normal)
|
|
||||||
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="priority">The priority of 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, JobPriority priority = JobPriority.Normal)
|
|
||||||
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>
|
|
||||||
/// <param name="priority">The priority of 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, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
|
||||||
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>
|
|
||||||
/// <param name="priority">The priority of 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, JobPriority priority = JobPriority.Normal)
|
|
||||||
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="dependency">The job that this job depends on.</param>
|
|
||||||
/// <param name="priority">The priority of 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, JobPriority priority = JobPriority.Normal)
|
|
||||||
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="priority">The priority of 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, JobPriority priority = JobPriority.Normal)
|
|
||||||
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>
|
|
||||||
/// Waits asynchronously until the specified job is completed, allowing the calling thread to perform other work while waiting.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handle">The handle of the job to wait for.</param>
|
|
||||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the wait operation.</param>
|
|
||||||
/// <returns>A task that represents the asynchronous wait operation.</returns>
|
|
||||||
Task WaitAsync(JobHandle handle, CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Waits asynchronously until all specified job handles have completed, allowing the calling thread to perform other work while waiting.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The collection handles will be reordered in-place to move completed handles to the front.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="handles">A read-only memory containing the job handles to monitor for completion.</param>
|
|
||||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the wait operation.</param>
|
|
||||||
/// <returns>A task that represents the asynchronous wait operation.</returns>
|
|
||||||
Task WaitAllAsync(Memory<JobHandle> handles, CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Waits asynchronously until any of the specified job handles has completed, allowing the calling thread to perform other work while waiting, and returns the first completed handle.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handles">A read-only memory containing the job handles to monitor for completion.</param>
|
|
||||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the wait operation.</param>
|
|
||||||
/// <returns>A task that represents the asynchronous wait operation.</returns>
|
|
||||||
Task<JobHandle> WaitAnyAsync(ReadOnlyMemory<JobHandle> handles, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
@@ -2,21 +2,33 @@ namespace Misaki.HighPerformance.Jobs;
|
|||||||
|
|
||||||
public readonly ref struct JobExecutionContext
|
public readonly ref struct JobExecutionContext
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the current thread executing the job.
|
||||||
|
/// </summary>
|
||||||
public int ThreadIndex
|
public int ThreadIndex
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IJobScheduler JobScheduler
|
/// <summary>
|
||||||
|
/// Gets the job scheduler that is responsible for managing the execution of jobs.
|
||||||
|
/// </summary>
|
||||||
|
public JobScheduler JobScheduler
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the state object for the job scheduler.
|
||||||
|
/// </summary>
|
||||||
public object? State
|
public object? State
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the handle for the currently executing job.
|
||||||
|
/// </summary>
|
||||||
public JobHandle SelfHandle
|
public JobHandle SelfHandle
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
|
|||||||
@@ -8,28 +8,112 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace Misaki.HighPerformance.Jobs;
|
namespace Misaki.HighPerformance.Jobs;
|
||||||
|
|
||||||
public struct JobSchedulerDesc
|
public struct JobSchedulerDesc()
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the number of worker threads to be created and managed by the job scheduler. If set to less than 1, at least one worker thread will be created.
|
||||||
|
/// </summary>
|
||||||
public int ThreadCount
|
public int ThreadCount
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the priority of the worker threads. This can be used to influence the scheduling of the threads by the operating system. The default value is <see cref="ThreadPriority.Normal"/>.
|
||||||
|
/// </summary>
|
||||||
public ThreadPriority ThreadPriority
|
public ThreadPriority ThreadPriority
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
} = ThreadPriority.Normal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the state object for the job scheduler. This can be used to store any user-defined data or context that may be needed by the jobs or worker threads. The job scheduler does not interpret or manage this state in any way; it is simply provided as a convenience for users of the job scheduler. The default value is null.
|
||||||
|
/// </summary>
|
||||||
public object? State
|
public object? State
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class WaitItem : IThreadPoolWorkItem
|
||||||
|
{
|
||||||
|
private readonly JobScheduler _scheduler;
|
||||||
|
private readonly JobHandle _jobHandle;
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource _completionSource;
|
||||||
|
|
||||||
|
public Task Task => _completionSource.Task;
|
||||||
|
|
||||||
|
public WaitItem(JobScheduler scheduler, JobHandle jobHandle, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_scheduler = scheduler;
|
||||||
|
_jobHandle = jobHandle;
|
||||||
|
_completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
cancellationToken.Register((cs, tk) => ((TaskCompletionSource)cs!).TrySetCanceled(tk), _completionSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_scheduler.Wait(_jobHandle);
|
||||||
|
_completionSource.SetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class WaitAllItem : IThreadPoolWorkItem
|
||||||
|
{
|
||||||
|
private readonly JobScheduler _scheduler;
|
||||||
|
private readonly Memory<JobHandle> _jobHandles;
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource _completionSource;
|
||||||
|
|
||||||
|
public Task Task => _completionSource.Task;
|
||||||
|
|
||||||
|
public WaitAllItem(JobScheduler scheduler, Memory<JobHandle> jobHandles, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_scheduler = scheduler;
|
||||||
|
_jobHandles = jobHandles;
|
||||||
|
_completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
cancellationToken.Register((cs, tk) => ((TaskCompletionSource)cs!).TrySetCanceled(tk), _completionSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_scheduler.WaitAll(_jobHandles.Span);
|
||||||
|
_completionSource.SetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class WaitAnyItem : IThreadPoolWorkItem
|
||||||
|
{
|
||||||
|
private readonly JobScheduler _scheduler;
|
||||||
|
private readonly ReadOnlyMemory<JobHandle> _jobHandles;
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource<JobHandle> _completionSource;
|
||||||
|
|
||||||
|
public Task<JobHandle> Task => _completionSource.Task;
|
||||||
|
|
||||||
|
public WaitAnyItem(JobScheduler scheduler, ReadOnlyMemory<JobHandle> jobHandles, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_scheduler = scheduler;
|
||||||
|
_jobHandles = jobHandles;
|
||||||
|
_completionSource = new TaskCompletionSource<JobHandle>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
cancellationToken.Register((cs, tk) => ((TaskCompletionSource)cs!).TrySetCanceled(tk), _completionSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
var completedHandle = _scheduler.WaitAny(_jobHandles.Span);
|
||||||
|
_completionSource.SetResult(completedHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <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>
|
||||||
public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
public sealed unsafe partial class JobScheduler : 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;
|
||||||
@@ -50,6 +134,9 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
internal object? State => _state;
|
internal object? State => _state;
|
||||||
internal bool IsCancellationRequested => _cts.IsCancellationRequested;
|
internal bool IsCancellationRequested => _cts.IsCancellationRequested;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of worker threads managed by the job scheduler.
|
||||||
|
/// </summary>
|
||||||
public int WorkerCount => _workerThreads.Length;
|
public int WorkerCount => _workerThreads.Length;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -186,7 +273,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Advance count to account for all dependencies upfront + 1 guard lock
|
// Advance count to account for all dependencies upfront + 1 guard lock
|
||||||
jobInfo.dependencyCount = validDepCount + 1;
|
Interlocked.Increment(ref jobInfo.dependencyCount);
|
||||||
|
|
||||||
var id = _jobInfoPool.Add(jobInfo, out var generation);
|
var id = _jobInfoPool.Add(jobInfo, out var generation);
|
||||||
ref var infoInPool = ref _jobInfoPool.GetElementReferenceAt(id, generation, out _);
|
ref var infoInPool = ref _jobInfoPool.GetElementReferenceAt(id, generation, out _);
|
||||||
@@ -392,7 +479,17 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
_jobInfoPool.Remove(handle.ID, handle.Generation);
|
_jobInfoPool.Remove(handle.ID, handle.Generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</param>
|
||||||
|
/// <param name="priority">The priority of 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>
|
||||||
|
public JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobPriority priority = JobPriority.Normal, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJob
|
where T : unmanaged, IJob
|
||||||
{
|
{
|
||||||
var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
|
var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
|
||||||
@@ -414,22 +511,47 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
jobRanges = JobRanges.Single,
|
jobRanges = JobRanges.Single,
|
||||||
};
|
};
|
||||||
|
|
||||||
return CreateJobHandle(ref jobInfo, threadIndex, dependency);
|
return CreateJobHandle(ref jobInfo, threadIndex, dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</param>
|
||||||
|
/// <param name="priority">The priority of 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>
|
||||||
|
public JobHandle Schedule<T>(ref readonly T job, JobPriority priority = JobPriority.Normal, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJob
|
where T : unmanaged, IJob
|
||||||
=> Schedule(in job, threadIndex, JobHandle.Invalid, priority);
|
=> Schedule(in job, -1, priority, dependencies);
|
||||||
|
|
||||||
public JobHandle Schedule<T>(ref readonly T job, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</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>
|
||||||
|
public JobHandle Schedule<T>(ref readonly T job, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJob
|
where T : unmanaged, IJob
|
||||||
=> Schedule(in job, -1, dependency, priority);
|
=> Schedule(in job, -1, JobPriority.Normal, dependencies);
|
||||||
|
|
||||||
public JobHandle Schedule<T>(ref readonly T job, JobPriority priority = JobPriority.Normal)
|
/// <summary>
|
||||||
where T : unmanaged, IJob
|
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
|
||||||
=> Schedule(in job, -1, JobHandle.Invalid, priority);
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
|
||||||
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</param>
|
||||||
|
/// <param name="priority">The priority of 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>
|
||||||
|
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobPriority priority = JobPriority.Normal, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJobParallelFor
|
where T : unmanaged, IJobParallelFor
|
||||||
{
|
{
|
||||||
var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
|
var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
|
||||||
@@ -459,22 +581,51 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return CreateJobHandle(ref jobInfo, threadIndex, dependency);
|
return CreateJobHandle(ref jobInfo, threadIndex, dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</param>
|
||||||
|
/// <param name="priority">The priority of 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>
|
||||||
|
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, JobPriority priority = JobPriority.Normal, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJobParallelFor
|
where T : unmanaged, IJobParallelFor
|
||||||
=> ScheduleParallelFor(in job, totalIteration, batchSize, threadIndex, JobHandle.Invalid, priority);
|
=> ScheduleParallelFor(in job, totalIteration, batchSize, -1, priority, dependencies);
|
||||||
|
|
||||||
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</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>
|
||||||
|
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJobParallelFor
|
where T : unmanaged, IJobParallelFor
|
||||||
=> ScheduleParallelFor(in job, totalIteration, batchSize, -1, dependency, priority);
|
=> ScheduleParallelFor(in job, totalIteration, batchSize, -1, JobPriority.Normal, dependencies);
|
||||||
|
|
||||||
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, JobPriority priority = JobPriority.Normal)
|
/// <summary>
|
||||||
where T : unmanaged, IJobParallelFor
|
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
|
||||||
=> ScheduleParallelFor(in job, totalIteration, batchSize, -1, JobHandle.Invalid, priority);
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
|
||||||
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</param>
|
||||||
|
/// <param name="priority">The priority of 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>
|
||||||
|
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobPriority priority = JobPriority.Normal, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJobParallel
|
where T : unmanaged, IJobParallel
|
||||||
{
|
{
|
||||||
var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
|
var pJobData = _freeList.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>());
|
||||||
@@ -504,21 +655,44 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return CreateJobHandle(ref jobInfo, threadIndex, dependency);
|
return CreateJobHandle(ref jobInfo, threadIndex, dependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</param>
|
||||||
|
/// <param name="priority">The priority of 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>
|
||||||
|
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, JobPriority priority = JobPriority.Normal, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJobParallel
|
where T : unmanaged, IJobParallel
|
||||||
=> ScheduleParallel(in job, totalIteration, batchSize, threadIndex, JobHandle.Invalid, priority);
|
=> ScheduleParallel(in job, totalIteration, batchSize, -1, priority, dependencies);
|
||||||
|
|
||||||
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, JobHandle dependency, JobPriority priority = JobPriority.Normal)
|
/// <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="dependencies">A collection of <see cref="JobHandle"/> representing the dependencies that must be completed before this job can begin.</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>
|
||||||
|
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, params ReadOnlySpan<JobHandle> dependencies)
|
||||||
where T : unmanaged, IJobParallel
|
where T : unmanaged, IJobParallel
|
||||||
=> ScheduleParallel(in job, totalIteration, batchSize, -1, dependency, priority);
|
=> ScheduleParallel(in job, totalIteration, batchSize, -1, JobPriority.Normal, dependencies);
|
||||||
|
|
||||||
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, JobPriority priority = JobPriority.Normal)
|
|
||||||
where T : unmanaged, IJobParallel
|
|
||||||
=> ScheduleParallel(in job, totalIteration, batchSize, -1, JobHandle.Invalid, priority);
|
|
||||||
|
|
||||||
|
/// <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>
|
||||||
public JobHandle CombineDependencies(params ReadOnlySpan<JobHandle> dependencies)
|
public JobHandle CombineDependencies(params ReadOnlySpan<JobHandle> dependencies)
|
||||||
{
|
{
|
||||||
var jobInfo = new JobInfo
|
var jobInfo = new JobInfo
|
||||||
@@ -535,6 +709,12 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
return CreateJobHandle(ref jobInfo, -1, dependencies);
|
return CreateJobHandle(ref jobInfo, -1, 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>
|
||||||
public JobState GetJobStatus(JobHandle handle)
|
public JobState GetJobStatus(JobHandle handle)
|
||||||
{
|
{
|
||||||
if (!handle.IsValid)
|
if (!handle.IsValid)
|
||||||
@@ -552,6 +732,10 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
return JobUtility.GetState(Volatile.Read(ref jobInfo.state));
|
return JobUtility.GetState(Volatile.Read(ref jobInfo.state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blocks the calling thread until the specified job is completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">The handle of the job to wait for.</param>
|
||||||
public void Wait(JobHandle handle)
|
public void Wait(JobHandle handle)
|
||||||
{
|
{
|
||||||
if (!handle.IsValid)
|
if (!handle.IsValid)
|
||||||
@@ -582,6 +766,13 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
public void WaitAll(params Span<JobHandle> handles)
|
public void WaitAll(params Span<JobHandle> handles)
|
||||||
{
|
{
|
||||||
if (handles.Length == 0)
|
if (handles.Length == 0)
|
||||||
@@ -617,6 +808,11 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
public JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles)
|
public JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles)
|
||||||
{
|
{
|
||||||
var spin = new SpinWait();
|
var spin = new SpinWait();
|
||||||
@@ -635,6 +831,12 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits asynchronously until the specified job is completed, allowing the calling thread to perform other work while waiting.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">The handle of the job to wait for.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the wait operation.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous wait operation.</returns>
|
||||||
public Task WaitAsync(JobHandle handle, CancellationToken cancellationToken = default)
|
public Task WaitAsync(JobHandle handle, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!handle.IsValid)
|
if (!handle.IsValid)
|
||||||
@@ -648,6 +850,15 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
return workItem.Task;
|
return workItem.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits asynchronously until all specified job handles have completed, allowing the calling thread to perform other work while waiting.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The collection handles will be reordered in-place to move completed handles to the front.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="handles">A memory containing the job handles to monitor for completion.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the wait operation.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous wait operation.</returns>
|
||||||
public Task WaitAllAsync(Memory<JobHandle> handles, CancellationToken cancellationToken = default)
|
public Task WaitAllAsync(Memory<JobHandle> handles, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (handles.Length == 0)
|
if (handles.Length == 0)
|
||||||
@@ -661,6 +872,12 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
|
|||||||
return workItem.Task;
|
return workItem.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits asynchronously until any of the specified job handles has completed, allowing the calling thread to perform other work while waiting, and returns the first completed handle.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handles">A read-only memory containing the job handles to monitor for completion.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the wait operation.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous wait operation.</returns>
|
||||||
public Task<JobHandle> WaitAnyAsync(ReadOnlyMemory<JobHandle> handles, CancellationToken cancellationToken = default)
|
public Task<JobHandle> WaitAnyAsync(ReadOnlyMemory<JobHandle> handles, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (handles.Length == 0)
|
if (handles.Length == 0)
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ internal class WorkerThread : IDisposable
|
|||||||
private readonly ConcurrentQueue<JobHandle>[] _localQueues;
|
private readonly ConcurrentQueue<JobHandle>[] _localQueues;
|
||||||
|
|
||||||
private readonly JobScheduler _scheduler;
|
private readonly JobScheduler _scheduler;
|
||||||
private readonly Random _stealRandom;
|
|
||||||
|
|
||||||
private readonly int _maxStealAttems;
|
private readonly int _maxStealAttems;
|
||||||
|
|
||||||
private uint _priorityTick;
|
private uint _priorityTick;
|
||||||
@@ -28,8 +26,6 @@ internal class WorkerThread : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
_scheduler = scheduler;
|
_scheduler = scheduler;
|
||||||
_stealRandom = new Random(index * 9973 + Environment.TickCount);
|
|
||||||
|
|
||||||
_maxStealAttems = Math.Max((int)(_scheduler.WorkerCount * 0.5f), 3);
|
_maxStealAttems = Math.Max((int)(_scheduler.WorkerCount * 0.5f), 3);
|
||||||
|
|
||||||
_thread = new Thread(WorkLoop)
|
_thread = new Thread(WorkLoop)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Misaki.HighPerformance.Collections;
|
|||||||
#endif
|
#endif
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.Versioning;
|
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
@@ -39,34 +38,34 @@ public readonly struct AllocationInfo
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct AllocationManagerInitOpts
|
public readonly struct AllocationManagerDesc
|
||||||
{
|
{
|
||||||
public nuint ArenaCapacity
|
public required nuint ArenaCapacity
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public nuint StackCapacity
|
public required nuint StackCapacity
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public nuint FreeListChunkSize
|
public required nuint FreeListChunkSize
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public nuint FreeListDefaultAlignment
|
public required nuint FreeListDefaultAlignment
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int FreeListConcurrencyLevel
|
public required int FreeListConcurrencyLevel
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AllocationManagerInitOpts Default => new AllocationManagerInitOpts
|
public static AllocationManagerDesc Default => new AllocationManagerDesc
|
||||||
{
|
{
|
||||||
ArenaCapacity = 1024 * 1024 * 1024, // 1 GB
|
ArenaCapacity = 1024 * 1024 * 1024, // 1 GB
|
||||||
StackCapacity = 16 * 1024 * 1024, // 16 MB per thread
|
StackCapacity = 16 * 1024 * 1024, // 16 MB per thread
|
||||||
@@ -137,13 +136,6 @@ public static unsafe class AllocationManager
|
|||||||
{
|
{
|
||||||
AlignedFree(ptr);
|
AlignedFree(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
private static bool IsValid(void* _, MemoryHandle handle)
|
|
||||||
{
|
|
||||||
return ContainsAllocation(handle);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static MemoryPool<VirtualArena, VirtualArena.CreationOptions> s_arenaAllocator;
|
internal static MemoryPool<VirtualArena, VirtualArena.CreationOptions> s_arenaAllocator;
|
||||||
@@ -156,6 +148,7 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
private static ConcurrentSlotMap<AllocationInfo> s_allocations = null!;
|
private static ConcurrentSlotMap<AllocationInfo> s_allocations = null!;
|
||||||
|
private static long s_totalAllocatedMemory;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -168,6 +161,13 @@ public static unsafe class AllocationManager
|
|||||||
0;
|
0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
public static nuint TotalAllocatedMemory =>
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
(nuint)s_totalAllocatedMemory;
|
||||||
|
#else
|
||||||
|
0;
|
||||||
|
#endif
|
||||||
|
|
||||||
private static volatile bool s_initialized;
|
private static volatile bool s_initialized;
|
||||||
|
|
||||||
private static nuint s_threadLocalStackSize;
|
private static nuint s_threadLocalStackSize;
|
||||||
@@ -219,7 +219,7 @@ public static unsafe class AllocationManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Initialize(AllocationManagerInitOpts opts)
|
public static void Initialize(AllocationManagerDesc opts)
|
||||||
{
|
{
|
||||||
if (s_initialized)
|
if (s_initialized)
|
||||||
{
|
{
|
||||||
@@ -318,6 +318,8 @@ public static unsafe class AllocationManager
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Interlocked.Add(ref s_totalAllocatedMemory, (long)size);
|
||||||
|
|
||||||
var id = s_allocations.Add(info, out var generation);
|
var id = s_allocations.Add(info, out var generation);
|
||||||
return new MemoryHandle(id, generation);
|
return new MemoryHandle(id, generation);
|
||||||
#else
|
#else
|
||||||
@@ -332,13 +334,17 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
if (newPtr == null)
|
if (newPtr == null)
|
||||||
{
|
{
|
||||||
s_allocations.Remove(handle.ID, handle.Generation);
|
s_allocations.Remove(handle.ID, handle.Generation, out var info);
|
||||||
|
Interlocked.Add(ref s_totalAllocatedMemory, -(long)info.Size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
if (s_allocations.TryGetElement(handle.ID, handle.Generation, out var oldInfo))
|
if (s_allocations.TryGetElement(handle.ID, handle.Generation, out var oldInfo))
|
||||||
{
|
{
|
||||||
|
var diff = newSize - oldInfo.Size;
|
||||||
|
Interlocked.Add(ref s_totalAllocatedMemory, (long)diff);
|
||||||
|
|
||||||
var newInfo = oldInfo with
|
var newInfo = oldInfo with
|
||||||
{
|
{
|
||||||
Address = (IntPtr)newPtr,
|
Address = (IntPtr)newPtr,
|
||||||
@@ -364,7 +370,14 @@ public static unsafe class AllocationManager
|
|||||||
{
|
{
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
return s_allocations.Remove(handle.ID, handle.Generation, out var info);
|
|
||||||
|
if (s_allocations.Remove(handle.ID, handle.Generation, out var info))
|
||||||
|
{
|
||||||
|
Interlocked.Add(ref s_totalAllocatedMemory, -(long)info.Size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
@@ -412,30 +425,6 @@ public static unsafe class AllocationManager
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the total newSize of all currently tracked allocations.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Always returns 0 if MHP_ENABLE_SAFETY_CHECKS is disabled.
|
|
||||||
/// </remarks>
|
|
||||||
/// <returns>The total newSize of all currently tracked allocations.</returns>
|
|
||||||
public static nuint GetTotalAllocatedMemory()
|
|
||||||
{
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
|
||||||
|
|
||||||
nuint total = 0;
|
|
||||||
foreach (var allocation in s_allocations)
|
|
||||||
{
|
|
||||||
total += allocation.Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return total;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
|
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -10,9 +10,41 @@ public struct BigStruct
|
|||||||
public long k, l, m, n, o, p, q, r, s, t;
|
public long k, l, m, n, o, p, q, r, s, t;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Integer
|
public class Data
|
||||||
{
|
{
|
||||||
|
public class Inner
|
||||||
|
{
|
||||||
public BigStruct value;
|
public BigStruct value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly WeakReference<Inner> _inner;
|
||||||
|
|
||||||
|
public Data(Inner inner)
|
||||||
|
{
|
||||||
|
_inner = new WeakReference<Inner>(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int A
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!_inner.TryGetTarget(out var inner))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Inner class instance is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return inner.value.a;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!_inner.TryGetTarget(out var inner))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Inner class instance is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.value.a = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConcurrentSlotMapBenchmark
|
public class ConcurrentSlotMapBenchmark
|
||||||
@@ -20,7 +52,8 @@ public class ConcurrentSlotMapBenchmark
|
|||||||
private readonly ConcurrentSlotMap<BigStruct> _concurrentMap = new ConcurrentSlotMap<BigStruct>(1000);
|
private readonly ConcurrentSlotMap<BigStruct> _concurrentMap = new ConcurrentSlotMap<BigStruct>(1000);
|
||||||
private readonly SlotMap<BigStruct> _slotMap = new SlotMap<BigStruct>(1000);
|
private readonly SlotMap<BigStruct> _slotMap = new SlotMap<BigStruct>(1000);
|
||||||
private readonly BigStruct[] _slots = new BigStruct[1000];
|
private readonly BigStruct[] _slots = new BigStruct[1000];
|
||||||
private readonly Integer[] _objects = new Integer[1000];
|
private readonly Data[] _objects = new Data[1000];
|
||||||
|
private readonly Data.Inner[] _inners = new Data.Inner[1000];
|
||||||
|
|
||||||
private readonly int2[] _randomIndices1 = new int2[1000];
|
private readonly int2[] _randomIndices1 = new int2[1000];
|
||||||
private readonly int2[] _randomIndices2 = new int2[1000];
|
private readonly int2[] _randomIndices2 = new int2[1000];
|
||||||
@@ -43,7 +76,8 @@ public class ConcurrentSlotMapBenchmark
|
|||||||
_randomIndices2[i] = new int2(id, generation);
|
_randomIndices2[i] = new int2(id, generation);
|
||||||
|
|
||||||
_slots[i] = element;
|
_slots[i] = element;
|
||||||
_objects[i] = new Integer { value = element };
|
_inners[i] = new Data.Inner { value = element };
|
||||||
|
_objects[i] = new Data(_inners[i]);
|
||||||
|
|
||||||
_randomSlots[i] = i;
|
_randomSlots[i] = i;
|
||||||
}
|
}
|
||||||
@@ -89,7 +123,7 @@ public class ConcurrentSlotMapBenchmark
|
|||||||
{
|
{
|
||||||
for (var i = 0; i < 1000; i++)
|
for (var i = 0; i < 1000; i++)
|
||||||
{
|
{
|
||||||
_objects[_randomSlots[i]].value.a += 1;
|
_objects[_randomSlots[i]].A += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public static class GlobalSetup
|
|||||||
[GlobalTestInitialize]
|
[GlobalTestInitialize]
|
||||||
public static void GlobalInitialize(TestContext ctx)
|
public static void GlobalInitialize(TestContext ctx)
|
||||||
{
|
{
|
||||||
AllocationManager.Initialize(AllocationManagerInitOpts.Default);
|
AllocationManager.Initialize(AllocationManagerDesc.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
[GlobalTestCleanup]
|
[GlobalTestCleanup]
|
||||||
|
|||||||
Reference in New Issue
Block a user