Refactor job API: add JobExecutionContext, update tests

Major breaking change: job interfaces now use JobExecutionContext
instead of threadIndex, enabling thread-aware and dynamic job
dispatching. Updated all job system, SPMD, and test code to match.
Collections improved with new methods and clearer enumerators.
Renamed IJobScheduler.WaitComplete to Wait. Incremented project
versions. Includes bug fixes, documentation, and style updates.
This commit is contained in:
2026-03-04 11:43:39 +09:00
parent b9ca71834f
commit 37d548085e
31 changed files with 652 additions and 207 deletions

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AssemblyVersion>1.3.1</AssemblyVersion> <AssemblyVersion>1.5.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>

View File

@@ -1,2 +1,2 @@
global using unsafe JobExecutionFunc = delegate*<void*, ref Misaki.HighPerformance.Jobs.JobRanges, ref int, int, bool>; global using unsafe JobExecutionFunc = delegate*<void*, ref Misaki.HighPerformance.Jobs.JobRanges, ref int, ref readonly Misaki.HighPerformance.Jobs.JobExecutionContext, bool>;

View File

@@ -8,8 +8,8 @@ public interface IJob
/// <summary> /// <summary>
/// Executes the job logic. /// Executes the job logic.
/// </summary> /// </summary>
/// <param name="threadIndex">The index of the thread executing the job, useful for thread-specific operations.</param> /// <param name="ctx">The context of the job execution, providing access to thread-specific information and job scheduling capabilities.</param>
void Execute(int threadIndex); void Execute(ref readonly JobExecutionContext ctx);
} }
/// <summary> /// <summary>
@@ -21,8 +21,8 @@ public interface IJobParallelFor
/// Executes the job for a single item at the given index. /// Executes the job for a single item at the given index.
/// </summary> /// </summary>
/// <param name="loopIndex">The index of the item to process.</param> /// <param name="loopIndex">The index of the item to process.</param>
/// <param name="threadIndex">The index of the thread executing the job, useful for thread-specific operations.</param> /// <param name="ctx">The context of the job execution, providing access to thread-specific information and job scheduling capabilities.</param>
void Execute(int loopIndex, int threadIndex); void Execute(int loopIndex, ref readonly JobExecutionContext ctx);
} }
/// <summary> /// <summary>
@@ -35,36 +35,36 @@ public interface IJobParallel
/// </summary> /// </summary>
/// <param name="startIndex">The zero-based index at which to begin the operation.</param> /// <param name="startIndex">The zero-based index at which to begin the operation.</param>
/// <param name="endIndex">The zero-based index at which to end the operation.</param> /// <param name="endIndex">The zero-based index at which to end the operation.</param>
/// <param name="threadIndex">The index of the thread executing the job, useful for thread-specific operations.</param> /// <param name="ctx">The context of the job execution, providing access to thread-specific information and job scheduling capabilities.</param>
void Execute(int startIndex, int endIndex, int threadIndex); void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx);
} }
public static class IJobExtensions public static class IJobExtensions
{ {
public static void Run<T>(this ref T job, int threadIndex) public static void Run<T>(this ref T job, ref readonly JobExecutionContext ctx)
where T : struct, IJob where T : struct, IJob
{ {
job.Execute(threadIndex); job.Execute(in ctx);
} }
} }
public static class IJobParallelForExtensions public static class IJobParallelForExtensions
{ {
public static void Run<T>(this ref T job, int totalIterations, int threadIndex) public static void Run<T>(this ref T job, int totalIterations, ref readonly JobExecutionContext ctx)
where T : struct, IJobParallelFor where T : struct, IJobParallelFor
{ {
for (int i = 0; i < totalIterations; i++) for (var i = 0; i < totalIterations; i++)
{ {
job.Execute(i, threadIndex); job.Execute(i, in ctx);
} }
} }
} }
public static class IJobParallelExtensions public static class IJobParallelExtensions
{ {
public static void Run<T>(this ref T job, int totalIterations, int threadIndex) public static void Run<T>(this ref T job, int totalIterations, ref readonly JobExecutionContext ctx)
where T : struct, IJobParallel where T : struct, IJobParallel
{ {
job.Execute(0, totalIterations, threadIndex); job.Execute(0, totalIterations, in ctx);
} }
} }

View File

@@ -0,0 +1,20 @@
namespace Misaki.HighPerformance.Jobs;
public readonly ref struct JobExecutionContext
{
public int ThreadIndex
{
get;
}
public IJobScheduler JobScheduler
{
get;
}
public JobExecutionContext(int threadIndex, IJobScheduler jobScheduler)
{
ThreadIndex = threadIndex;
JobScheduler = jobScheduler;
}
}

View File

@@ -2,11 +2,11 @@ namespace Misaki.HighPerformance.Jobs;
internal static unsafe class JobExecutor internal static unsafe class JobExecutor
{ {
public static bool Execute<T>(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, int threadIndex) public static bool Execute<T>(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, ref readonly JobExecutionContext ctx)
where T : unmanaged, IJob where T : unmanaged, IJob
{ {
var pJob = (T*)pJobData; var pJob = (T*)pJobData;
pJob->Execute(threadIndex); pJob->Execute(in ctx);
return Interlocked.Decrement(ref remainingBatches) == 0; return Interlocked.Decrement(ref remainingBatches) == 0;
} }
@@ -25,7 +25,7 @@ internal static unsafe class JobExecutor
return true; return true;
} }
public static bool ExecuteParallelFor<T>(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, int threadIndex) public static bool ExecuteParallelFor<T>(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, ref readonly JobExecutionContext ctx)
where T : unmanaged, IJobParallelFor where T : unmanaged, IJobParallelFor
{ {
var pJob = (T*)pJobData; var pJob = (T*)pJobData;
@@ -40,7 +40,7 @@ internal static unsafe class JobExecutor
for (var i = start; i < end; i++) for (var i = start; i < end; i++)
{ {
pJob->Execute(i, threadIndex); pJob->Execute(i, in ctx);
} }
if (Interlocked.Decrement(ref remainingBatches) == 0) if (Interlocked.Decrement(ref remainingBatches) == 0)
@@ -52,7 +52,7 @@ internal static unsafe class JobExecutor
return wasTheLastBatch; return wasTheLastBatch;
} }
public static bool ExecuteParallel<T>(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, int threadIndex) public static bool ExecuteParallel<T>(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, ref readonly JobExecutionContext ctx)
where T : unmanaged, IJobParallel where T : unmanaged, IJobParallel
{ {
var pJob = (T*)pJobData; var pJob = (T*)pJobData;
@@ -65,7 +65,7 @@ internal static unsafe class JobExecutor
break; break;
} }
pJob->Execute(start, end, threadIndex); pJob->Execute(start, end, in ctx);
if (Interlocked.Decrement(ref remainingBatches) == 0) if (Interlocked.Decrement(ref remainingBatches) == 0)
{ {
wasTheLastBatch = true; wasTheLastBatch = true;

View File

@@ -1,7 +1,9 @@
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Jobs; namespace Misaki.HighPerformance.Jobs;
@@ -189,27 +191,18 @@ public interface IJobScheduler
/// Blocks the calling thread until the specified job is completed. /// Blocks the calling thread until the specified job is completed.
/// </summary> /// </summary>
/// <param name="handle">The handle of the job to wait for.</param> /// <param name="handle">The handle of the job to wait for.</param>
void WaitComplete(JobHandle handle); void Wait(JobHandle handle);
/// <summary> /// <summary>
/// Blocks the calling thread until all specified job handles have completed. /// Blocks the calling thread until all specified job handles have completed.
/// </summary> /// </summary>
/// <remarks>This method waits for all jobs referenced by the provided handles to complete before /// <param name="handles">A collection of job handles to wait for.</param>
/// returning. The calling thread will be blocked until every job has finished. If any handle is invalid or does not
/// correspond to an active job, it is considered completed. This method is not thread-safe and should not be called
/// concurrently from multiple threads.</remarks>
/// <param name="handles">A collection of job handles to wait for. Each handle represents an asynchronous job whose completion is awaited.
/// The collection must not be empty.</param>
void WaitAll(params ReadOnlySpan<JobHandle> handles); void WaitAll(params ReadOnlySpan<JobHandle> handles);
/// <summary> /// <summary>
/// Waits until any of the specified job handles has completed and returns the first completed handle. /// Waits until any of the specified job handles has completed and returns the first completed handle.
/// </summary> /// </summary>
/// <remarks>This method blocks the calling thread until at least one of the specified jobs has finished. /// <param name="handles">A read-only span containing the job handles to monitor for completion.</param>
/// The returned handle corresponds to the job that completed first among those provided. The order of handles in
/// the span may affect which handle is returned if multiple jobs complete simultaneously.</remarks>
/// <param name="handles">A read-only span containing the job handles to monitor for completion. Each handle represents a job whose
/// completion status will be checked.</param>
/// <returns>The first job handle from the provided collection that has completed.</returns> /// <returns>The first job handle from the provided collection that has completed.</returns>
JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles); JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles);
} }
@@ -730,7 +723,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
return (JobState)(Volatile.Read(ref Unsafe.As<JobState, int>(ref jobInfo.state)) & _STATE_MASK); return (JobState)(Volatile.Read(ref Unsafe.As<JobState, int>(ref jobInfo.state)) & _STATE_MASK);
} }
public void WaitComplete(JobHandle handle) public void Wait(JobHandle handle)
{ {
if (!handle.IsValid) if (!handle.IsValid)
{ {
@@ -756,15 +749,29 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
public void WaitAll(params ReadOnlySpan<JobHandle> handles) public void WaitAll(params ReadOnlySpan<JobHandle> handles)
{ {
if (handles.Length == 0)
{
return;
}
using var orderedHandles = new UnsafeArray<JobHandle>(handles.Length, Allocator.Temp);
var spin = new SpinWait(); var spin = new SpinWait();
var completedCount = 0;
orderedHandles.CopyFrom(handles);
while (true) while (true)
{ {
var completedCount = 0; for (int i = completedCount; i < orderedHandles.Length; i++)
foreach (var handle in handles)
{ {
var handle = orderedHandles[i];
if (!_jobInfoPool.Contains(handle.ID, handle.Generation)) if (!_jobInfoPool.Contains(handle.ID, handle.Generation))
{ {
// Move completed handle to the front (completedCount index) to avoid checking it again.
var temp = orderedHandles[completedCount];
orderedHandles[completedCount] = handle;
orderedHandles[i] = temp;
completedCount++; completedCount++;
} }
} }

View File

@@ -103,11 +103,17 @@ 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 && Interlocked.CompareExchange(ref jobInfo.state, JobState.Running, JobState.Scheduled) == JobState.Scheduled) if (exist && Interlocked.CompareExchange(ref jobInfo.state, JobState.Running, JobState.Scheduled) == JobState.Scheduled)
{ {
if (jobInfo.pExecutionFunc == null if (jobInfo.pExecutionFunc != null)
|| jobInfo.pExecutionFunc(jobInfo.pJobData, ref jobInfo.jobRanges, ref jobInfo.remainingBatches, _index))
{ {
_scheduler.MarkJobComplete(handle); var ctx = new JobExecutionContext(_index, _scheduler);
if (!jobInfo.pExecutionFunc(jobInfo.pJobData, ref jobInfo.jobRanges, ref jobInfo.remainingBatches, in ctx))
{
// If the job returns false, it means it we are not the last worker to process this job, so we should not mark it as complete yet.
continue;
}
} }
_scheduler.MarkJobComplete(handle);
} }
} }
} }

View File

@@ -7,7 +7,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.3.9</AssemblyVersion> <AssemblyVersion>1.4.0</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<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>

View File

@@ -176,7 +176,7 @@ public static unsafe class AllocationManager
} }
} }
private struct StackAllocator : IAllocator private struct StackAllocator : IAllocator, IDisposable
{ {
private const int _STACK_MAGIC_ID = -6843541; private const int _STACK_MAGIC_ID = -6843541;
@@ -257,6 +257,11 @@ public static unsafe class AllocationManager
{ {
return s_stack.CreateScope(pSelf->_handle); return s_stack.CreateScope(pSelf->_handle);
} }
public readonly void Dispose()
{
Stack.DisposeAll();
}
} }
private const uint _DEFAULT_MEMORY_POOL_SIZE = 1024 * 1024; // 1 MB private const uint _DEFAULT_MEMORY_POOL_SIZE = 1024 * 1024; // 1 MB
@@ -313,10 +318,16 @@ public static unsafe class AllocationManager
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static GCHandle HeaderGetHandle(AllocationHeader* header) => header->stackHandle; private static GCHandle HeaderGetHandle(AllocationHeader* header)
{
return header->stackHandle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void HeaderSetHandle(AllocationHeader* header, GCHandle handle) => header->stackHandle = handle; private static void HeaderSetHandle(AllocationHeader* header, GCHandle handle)
{
header->stackHandle = handle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void HeaderFreeHandle(AllocationHeader* header) private static void HeaderFreeHandle(AllocationHeader* header)
@@ -691,9 +702,10 @@ public static unsafe class AllocationManager
{ {
throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot)); throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot));
} }
}
if (LiveAllocationCount != 0) Debug.Assert(LiveAllocationCount == 0);
}
else if (LiveAllocationCount != 0)
{ {
throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations."); throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations.");
} }
@@ -702,8 +714,7 @@ public static unsafe class AllocationManager
if (s_pArenaAllocator != null) if (s_pArenaAllocator != null)
{ {
s_pArenaAllocator->Dispose(); s_pArenaAllocator->Dispose();
s_pStackAllocator->Dispose();
Stack.DisposeAll();
NativeMemory.Free(s_pArenaAllocator); NativeMemory.Free(s_pArenaAllocator);
} }

View File

@@ -9,7 +9,7 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct HashMapHelper<TKey> : IDisposable public unsafe struct HashMapHelper<TKey> : IDisposable
where TKey : unmanaged, IEquatable<TKey> where TKey : unmanaged, IEquatable<TKey>
{ {
internal unsafe struct Enumerator internal struct Enumerator
{ {
public HashMapHelper<TKey>* buffer; public HashMapHelper<TKey>* buffer;
public int index; public int index;
@@ -192,11 +192,17 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return result; return result;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int GetBucket(int hash)
{
return hash & (_bucketCapacity - 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int GetBucket(in TKey key) private readonly int GetBucket(in TKey key)
{ {
var h = (uint)key.GetHashCode(); var h = key.GetHashCode();
return (int)(h & (uint)(_bucketCapacity - 1)); return GetBucket(h);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -431,6 +437,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
where TValue : unmanaged where TValue : unmanaged
{ {
ThrowIfNotCreated(); ThrowIfNotCreated();
var idx = Find(key); var idx = Find(key);
if (idx != -1) if (idx != -1)
{ {
@@ -442,6 +449,82 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return ref Unsafe.NullRef<TValue>(); return ref Unsafe.NullRef<TValue>();
} }
public ref TValue GetValueRefOrAddDefault<TValue>(in TKey key, out bool exists)
where TValue : unmanaged
{
ThrowIfNotCreated();
var idx = -1;
var bucket = -1;
var hash = key.GetHashCode();
if (_allocatedIndex > 0)
{
// First find the slot based on the hash
bucket = GetBucket(hash);
var entryIdx = _buckets[bucket];
if ((uint)entryIdx < (uint)_capacity)
{
var nextPtrs = _next;
while (!UnsafeUtility.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
{
entryIdx = nextPtrs[entryIdx];
if ((uint)entryIdx >= (uint)_capacity)
{
goto Found;
}
}
idx = entryIdx;
goto Found;
}
}
Found:
if (idx != -1)
{
exists = true;
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
}
int* next;
if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
{
var newCap = CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth));
Resize(newCap);
}
idx = _firstFreeIndex;
if (idx >= 0)
{
_firstFreeIndex = _next[idx];
}
else
{
idx = _allocatedIndex++;
}
CheckIndexOutOfBounds(idx);
UnsafeUtility.WriteArrayElement(_keys, idx, key);
bucket = GetBucket(hash);
// Add the index to the hash-map
next = _next;
next[idx] = _buckets[bucket];
_buckets[bucket] = idx;
_count++;
UnsafeUtility.WriteArrayElement(_buffer, idx, default(TValue));
exists = false;
return ref UnsafeUtility.ReadArrayElementRef<TValue>(_buffer, idx);
}
public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index) public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index)
{ {
ThrowIfNotCreated(); ThrowIfNotCreated();

View File

@@ -73,9 +73,20 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
} }
} }
public Enumerator GetEnumerator() => new Enumerator(in this); public Enumerator GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return new Enumerator(in this);
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public ReadOnlyUnsafeCollection(T* buffer, int count) public ReadOnlyUnsafeCollection(T* buffer, int count)
{ {

View File

@@ -122,9 +122,20 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
} }
} }
public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary> /// <summary>
/// Invalid constructor, use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> or <see cref="UnsafeArray(int, ref AllocationHandle, AllocationOption)"/> instead. /// Invalid constructor, use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> or <see cref="UnsafeArray(int, ref AllocationHandle, AllocationOption)"/> instead.

View File

@@ -7,7 +7,8 @@ using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections; namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValuePair<TKey, TValue>> public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValuePair<TKey, TValue>>
where TKey : unmanaged, IEquatable<TKey> where TValue : unmanaged where TKey : unmanaged, IEquatable<TKey>
where TValue : unmanaged
{ {
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
{ {
@@ -22,9 +23,15 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => _enumerator.MoveNext(); public bool MoveNext()
{
return _enumerator.MoveNext();
}
public void Reset() => _enumerator.Reset(); public void Reset()
{
_enumerator.Reset();
}
public void Dispose() public void Dispose()
{ {
@@ -68,9 +75,20 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
} }
} }
public Enumerator GetEnumerator() => new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator()
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => GetEnumerator(); {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary> /// <summary>
/// Invalid constructor, use <see cref="UnsafeHashMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashMap(int, ref AllocationHandle, AllocationOption)"/> instead. /// Invalid constructor, use <see cref="UnsafeHashMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashMap(int, ref AllocationHandle, AllocationOption)"/> instead.
@@ -167,6 +185,11 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
return ref _helper.GetValueRef<TValue>(key, out exists); return ref _helper.GetValueRef<TValue>(key, out exists);
} }
public ref TValue GetValueRefOrAddDefault(in TKey key, out bool exists)
{
return ref _helper.GetValueRefOrAddDefault<TValue>(key, out exists);
}
/// <summary> /// <summary>
/// Returns true if a given key is present in this hash map. /// Returns true if a given key is present in this hash map.
/// </summary> /// </summary>
@@ -180,7 +203,10 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// <summary> /// <summary>
/// Sets the capacity to match what it would be if it had been originally initialized with all its entries. /// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
/// </summary> /// </summary>
public void TrimExcess() => _helper.TrimExcess(); public void TrimExcess()
{
_helper.TrimExcess();
}
public void Resize(int newSize, AllocationOption option = AllocationOption.None) public void Resize(int newSize, AllocationOption option = AllocationOption.None)
{ {
@@ -196,20 +222,29 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeHashCollection<KeyValu
/// Retrieves an array of keys from the hash map. /// Retrieves an array of keys from the hash map.
/// </summary> /// </summary>
/// <returns>An array containing the keys stored in the hash map.</returns> /// <returns>An array containing the keys stored in the hash map.</returns>
public UnsafeArray<TKey> GetKeyArray(Allocator allocator) => _helper.GetKeyArray(allocator); public UnsafeArray<TKey> GetKeyArray(Allocator allocator)
{
return _helper.GetKeyArray(allocator);
}
/// <summary> /// <summary>
/// Retrieves an array of values from the underlying hash map. /// Retrieves an array of values from the underlying hash map.
/// </summary> /// </summary>
/// <returns>An UnsafeArray containing the values stored in the hash map.</returns> /// <returns>An UnsafeArray containing the values stored in the hash map.</returns>
public UnsafeArray<TValue> GetValueArray(Allocator allocator) => _helper.GetValueArray<TValue>(allocator); public UnsafeArray<TValue> GetValueArray(Allocator allocator)
{
return _helper.GetValueArray<TValue>(allocator);
}
/// <summary> /// <summary>
/// Retrieves an array of key-value pairs from the hash map. The keys are of type TKey and the values are of type /// Retrieves an array of key-value pairs from the hash map. The keys are of type TKey and the values are of type
/// TValue. /// TValue.
/// </summary> /// </summary>
/// <returns>Returns an UnsafeArray containing KeyValuePair objects.</returns> /// <returns>Returns an UnsafeArray containing KeyValuePair objects.</returns>
public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays(Allocator allocator) => _helper.GetKeyValueArrays<TValue>(allocator); public UnsafeArray<KeyValuePair<TKey, TValue>> GetKeyValueArrays(Allocator allocator)
{
return _helper.GetKeyValueArrays<TValue>(allocator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr() public readonly void* GetUnsafePtr()

View File

@@ -27,9 +27,15 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeHashCollection<T>, IEnumerable<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => _enumerator.MoveNext(); public bool MoveNext()
{
return _enumerator.MoveNext();
}
public void Reset() => _enumerator.Reset(); public void Reset()
{
_enumerator.Reset();
}
public readonly void Dispose() public readonly void Dispose()
{ {
@@ -42,9 +48,20 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeHashCollection<T>, IEnumerable<T>
public readonly int Capacity => _helper.Capacity; public readonly int Capacity => _helper.Capacity;
public readonly bool IsCreated => _helper.IsCreated; public readonly bool IsCreated => _helper.IsCreated;
public Enumerator GetEnumerator() => new((HashMapHelper<T>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return new((HashMapHelper<T>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary> /// <summary>
/// Invalid constructor. Use <see cref="UnsafeHashSet(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashSet(int, ref AllocationHandle, AllocationOption)"/> instead."/> /// Invalid constructor. Use <see cref="UnsafeHashSet(int, Allocator, AllocationOption)"/> or <see cref="UnsafeHashSet(int, ref AllocationHandle, AllocationOption)"/> instead."/>
@@ -97,7 +114,10 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeHashCollection<T>, IEnumerable<T>
/// <summary> /// <summary>
/// Sets the capacity to match what it would be if it had been originally initialized with all its entries. /// Sets the capacity to match what it would be if it had been originally initialized with all its entries.
/// </summary> /// </summary>
public void TrimExcess() => _helper.TrimExcess(); public void TrimExcess()
{
_helper.TrimExcess();
}
/// <summary> /// <summary>
/// Returns an array with a copy of this set's values (in no particular order). /// Returns an array with a copy of this set's values (in no particular order).

View File

@@ -22,7 +22,7 @@ internal class UnsafeListDebugView<T>
get get
{ {
var array = new T[_list.Count]; var array = new T[_list.Count];
for (int i = 0; i < _list.Count; i++) for (var i = 0; i < _list.Count; i++)
{ {
array[i] = _list[i]; array[i] = _list[i];
} }
@@ -71,20 +71,56 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
} }
} }
/// <summary>
/// A Parallel reader for an UnsafeList.
/// </summary>
/// <remarks>
/// Use <see cref="AsParallelReader"/> to create a parallel reader for a list.
/// The list must live at least as long as the parallel reader, and the parallel reader must not be used after the list is disposed.
/// </remarks>
public readonly unsafe struct ParallelReader
{
public readonly UnsafeList<T>* listData;
public readonly int Count => listData->_count;
public ref readonly T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref listData->_array[index];
}
public ref readonly T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref listData->_array[index];
}
internal ParallelReader(UnsafeList<T>* list)
{
listData = list;
}
public readonly Enumerator GetEnumerator()
{
return new Enumerator(listData);
}
public readonly ReadOnlySpan<T> AsSpan()
{
return new ReadOnlySpan<T>(listData->_array.GetUnsafePtr(), listData->_count);
}
}
/// <summary> /// <summary>
/// A parallel writer for an UnsafeList. /// A parallel writer for an UnsafeList.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list. /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a list.
/// The list must live at least as long as the parallel writer, and the parallel writer must not be used after the list is disposed.
/// </remarks> /// </remarks>
public unsafe struct ParallelWriter public readonly unsafe struct ParallelWriter
{ {
private volatile int _resizeLock; public readonly UnsafeList<T>* listData;
/// <summary>
/// The UnsafeList to write to.
/// </summary>
public UnsafeList<T>* listData;
internal ParallelWriter(UnsafeList<T>* list) internal ParallelWriter(UnsafeList<T>* list)
{ {
@@ -139,23 +175,6 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
get => ref _array[index]; get => ref _array[index];
} }
public Enumerator GetEnumerator() => new ((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Provides a parallel writer for the current list, enabling thread-safe additions to the list.
/// </summary>
/// <returns>A <see cref="ParallelWriter"/> instance that can be used to add items to the list in a thread-safe manner.</returns>
public ParallelWriter AsParallelWriter() => new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
/// <summary>
/// Converts the current list to an UnsafeArray representation.
/// </summary>
/// <returns>A new <see cref="UnsafeArray{T}"/> instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray() => new((T*)_array.GetUnsafePtr(), _count);
/// <summary> /// <summary>
/// Invalid constructor, use <see cref="UnsafeList(int, Allocator, AllocationOption)"/> or <see cref="UnsafeList(int, ref AllocationHandle, AllocationOption)"/> instead. /// Invalid constructor, use <see cref="UnsafeList(int, Allocator, AllocationOption)"/> or <see cref="UnsafeList(int, ref AllocationHandle, AllocationOption)"/> instead.
/// </summary> /// </summary>
@@ -196,9 +215,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
{ {
if (index + count > Capacity) if (index + count > Capacity)
{ {
throw new Exception( throw new Exception($"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Count}), requested count {count}!");
$"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Count}), requested count {count}!"
);
} }
} }
@@ -225,6 +242,66 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
} }
} }
public Enumerator GetEnumerator()
{
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Provides a parallel reader for the current list, enabling thread-safe read operations.
/// </summary>
/// <remarks>
/// The list must live at least as long as the parallel reader, and the parallel reader must not be used after the list is disposed.
/// For example, if you need to access the list in job system and wait that job in another stack frame, please always allocate the list struct itself on heap.
/// Otherwise the parallel reader will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid.
/// </remarks>
/// <returns>A <see cref="ParallelReader"/> instance that can be used to read items from the list in a thread-safe manner.</returns>
public ParallelReader AsParallelReader()
{
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
}
/// <summary>
/// Provides a parallel writer for the current list, enabling thread-safe additions to the list.
/// </summary>
/// <remarks>
/// The list must live at least as long as the parallel writer, and the parallel writer must not be used after the list is disposed.
/// For example, if you need to access the list in job system and wait that job in another stack frame, please always allocate the list struct itself on heap.
/// Otherwise the parallel writer will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid.
/// </remarks>
/// <returns>A <see cref="ParallelWriter"/> instance that can be used to add items to the list in a thread-safe manner.</returns>
public ParallelWriter AsParallelWriter()
{
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
}
/// <summary>
/// Converts the current list to an UnsafeArray representation.
/// </summary>
/// <remarks>
/// The returned <see cref="UnsafeArray{T}"/> shares the same underlying data as the list and does not own the memory.
/// </remarks>
/// <returns>A new <see cref="UnsafeArray{T}"/> instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray()
{
return new UnsafeArray<T>((T*)_array.GetUnsafePtr(), _count);
}
/// <summary>
/// Converts the current list to a read-only collection that provides unsafe access to its elements.
/// </summary>
/// <returns>A new <see cref="ReadOnlyUnsafeCollection{T}"/> instance that allows for read-only access to the list's elements without copying.</returns>
public readonly ReadOnlyUnsafeCollection<T> AsReadOnly() public readonly ReadOnlyUnsafeCollection<T> AsReadOnly()
{ {
return new ReadOnlyUnsafeCollection<T>((T*)_array.GetUnsafePtr(), _count); return new ReadOnlyUnsafeCollection<T>((T*)_array.GetUnsafePtr(), _count);

View File

@@ -62,9 +62,20 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
set => _array[index] = value; set => _array[index] = value;
} }
public Enumerator GetEnumerator() => new((UnsafeQueue<T>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return new((UnsafeQueue<T>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary> /// <summary>
/// Invalid constructor. Use <see cref="UnsafeQueue(int, Allocator, AllocationOption)"/> or <see cref="UnsafeQueue(int, ref AllocationHandle, AllocationOption)"/> instead."/> /// Invalid constructor. Use <see cref="UnsafeQueue(int, Allocator, AllocationOption)"/> or <see cref="UnsafeQueue(int, ref AllocationHandle, AllocationOption)"/> instead."/>

View File

@@ -86,9 +86,20 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
public readonly bool IsCreated => _data.IsCreated && _generations.IsCreated && _freeSlots.IsCreated && _validBits.IsCreated; public readonly bool IsCreated => _data.IsCreated && _generations.IsCreated && _freeSlots.IsCreated && _validBits.IsCreated;
public Enumerator GetEnumerator() => new((UnsafeSlotMap<T>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return new((UnsafeSlotMap<T>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary> /// <summary>
/// Invalid constructor. Use <see cref="UnsafeSlotMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeSlotMap(int, ref AllocationHandle, AllocationOption)"/> instead."/> /// Invalid constructor. Use <see cref="UnsafeSlotMap(int, Allocator, AllocationOption)"/> or <see cref="UnsafeSlotMap(int, ref AllocationHandle, AllocationOption)"/> instead."/>

View File

@@ -89,9 +89,20 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
public readonly int Capacity => _capacity; public readonly int Capacity => _capacity;
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated; public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated;
public Enumerator GetEnumerator() => new((UnsafeSparseSet<T>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return new((UnsafeSparseSet<T>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary> /// <summary>
/// Constructs an UnsafeSparseSet with a default size of 1 and uses the Persistent allocator. /// Constructs an UnsafeSparseSet with a default size of 1 and uses the Persistent allocator.

View File

@@ -81,9 +81,20 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
public readonly int Capacity => _array.Count; public readonly int Capacity => _array.Count;
public readonly bool IsCreated => _array.IsCreated; public readonly bool IsCreated => _array.IsCreated;
public Enumerator GetEnumerator() => new((UnsafeStack<T>*)UnsafeUtility.AddressOf(ref this)); public Enumerator GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator(); {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); return new((UnsafeStack<T>*)UnsafeUtility.AddressOf(ref this));
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary> /// <summary>
/// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, ref AllocationHandle, AllocationOption)"/> instead. /// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, ref AllocationHandle, AllocationOption)"/> instead.

View File

@@ -24,6 +24,11 @@ public readonly unsafe struct SharedPtr<T> : IEquatable<SharedPtr<T>>
return _value; return _value;
} }
public ref T GetRef()
{
return ref *_value;
}
public bool Equals(SharedPtr<T> other) public bool Equals(SharedPtr<T> other)
{ {
return _value == other._value; return _value == other._value;
@@ -81,11 +86,16 @@ public unsafe struct UniquePtr<T> : IEquatable<UniquePtr<T>>
_value = value; _value = value;
} }
public readonly T* Get() public T* Get()
{ {
return _value; return _value;
} }
public ref T GetRef()
{
return ref *_value;
}
public readonly SharedPtr<T> Share() public readonly SharedPtr<T> Share()
{ {
return new SharedPtr<T>(_value); return new SharedPtr<T>(_value);

View File

@@ -15,23 +15,33 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> LoadVector128(ref byte start, nuint offset) private static Vector128<byte> LoadVector128(ref byte start, nuint offset)
=> Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset)); {
return Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<byte> LoadVector256(ref byte start, nuint offset) private static Vector256<byte> LoadVector256(ref byte start, nuint offset)
=> Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.AddByteOffset(ref start, offset)); {
return Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector128SpanLength(nuint offset, int length) private static nuint GetByteVector128SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector128<byte>.Count - 1)); {
return (uint)((length - (int)offset) & ~(Vector128<byte>.Count - 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector256SpanLength(nuint offset, int length) private static nuint GetByteVector256SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector256<byte>.Count - 1)); {
return (uint)((length - (int)offset) & ~(Vector256<byte>.Count - 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector512SpanLength(nuint offset, int length) private static nuint GetByteVector512SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector512<byte>.Count - 1)); {
return (uint)((length - (int)offset) & ~(Vector512<byte>.Count - 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe nuint UnalignedCountVector128(byte* searchSpace) private static unsafe nuint UnalignedCountVector128(byte* searchSpace)

View File

@@ -6,7 +6,7 @@ namespace Misaki.HighPerformance.Mathematics.SPMD;
public interface IJobSPMD<TNumber> public interface IJobSPMD<TNumber>
where TNumber : unmanaged, INumber<TNumber>, IBinaryNumber<TNumber>, IMinMaxValue<TNumber>, IBitwiseOperators<TNumber, TNumber, TNumber> where TNumber : unmanaged, INumber<TNumber>, IBinaryNumber<TNumber>, IMinMaxValue<TNumber>, IBitwiseOperators<TNumber, TNumber, TNumber>
{ {
void Execute<TLane>(int baseIndex, int threadIndex) void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, TNumber>; where TLane : ISPMD<TLane, TNumber>;
} }
@@ -17,20 +17,20 @@ internal struct SPMDJobWrapper<T, TNumber> : IJobParallelFor
public T innerJob; public T innerJob;
public int totalCount; public int totalCount;
public void Execute(int loopIndex, int threadIndex) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
var baseIndex = loopIndex * WideLane<TNumber>.LaneWidth; var baseIndex = loopIndex * WideLane<TNumber>.LaneWidth;
var remaining = totalCount - baseIndex; var remaining = totalCount - baseIndex;
if (remaining >= WideLane<TNumber>.LaneWidth) if (remaining >= WideLane<TNumber>.LaneWidth)
{ {
innerJob.Execute<WideLane<TNumber>>(baseIndex, threadIndex); innerJob.Execute<WideLane<TNumber>>(baseIndex, in ctx);
} }
else else
{ {
for (var j = 0; j < remaining; j++) for (var j = 0; j < remaining; j++)
{ {
innerJob.Execute<ScalarLane<TNumber>>(baseIndex + j, threadIndex); innerJob.Execute<ScalarLane<TNumber>>(baseIndex + j, in ctx);
} }
} }
} }
@@ -43,15 +43,15 @@ internal struct SPMDScalerJobWrapper<T, TNumber> : IJobParallelFor
public T innerJob; public T innerJob;
public int totalCount; public int totalCount;
public void Execute(int loopIndex, int threadIndex) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
innerJob.Execute<ScalarLane<TNumber>>(loopIndex, threadIndex); innerJob.Execute<ScalarLane<TNumber>>(loopIndex, in ctx);
} }
} }
public static class IJobParallelForSPMDExtensions public static class IJobParallelForSPMDExtensions
{ {
public static void Run<T, TNumber>(this ref T job, int totalCount, int threadIndex) public static void Run<T, TNumber>(this ref T job, int totalCount, ref readonly JobExecutionContext ctx)
where T : struct, IJobSPMD<TNumber> where T : struct, IJobSPMD<TNumber>
where TNumber : unmanaged, INumber<TNumber>, IBinaryNumber<TNumber>, IMinMaxValue<TNumber>, IBitwiseOperators<TNumber, TNumber, TNumber> where TNumber : unmanaged, INumber<TNumber>, IBinaryNumber<TNumber>, IMinMaxValue<TNumber>, IBitwiseOperators<TNumber, TNumber, TNumber>
{ {
@@ -65,13 +65,13 @@ public static class IJobParallelForSPMDExtensions
if (remaining >= WideLane<TNumber>.LaneWidth) if (remaining >= WideLane<TNumber>.LaneWidth)
{ {
job.Execute<WideLane<TNumber>>(baseIndex, threadIndex); job.Execute<WideLane<TNumber>>(baseIndex, in ctx);
} }
else else
{ {
for (var i = 0; i < remaining; i++) for (var i = 0; i < remaining; i++)
{ {
job.Execute<ScalarLane<TNumber>>(baseIndex + i, threadIndex); job.Execute<ScalarLane<TNumber>>(baseIndex + i, in ctx);
} }
} }
} }
@@ -80,7 +80,7 @@ public static class IJobParallelForSPMDExtensions
{ {
for (var loopIndex = 0; loopIndex < totalCount; loopIndex++) for (var loopIndex = 0; loopIndex < totalCount; loopIndex++)
{ {
job.Execute<ScalarLane<TNumber>>(loopIndex, threadIndex); job.Execute<ScalarLane<TNumber>>(loopIndex, in ctx);
} }
} }
} }

View File

@@ -7,7 +7,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.0.0</AssemblyVersion> <AssemblyVersion>1.1.0</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<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>

View File

@@ -42,7 +42,7 @@ public class ParallelNoiseBenchmark
}; };
var handle = _jobScheduler.ScheduleParallel(ref job, _LENGTH, 64, -1, JobHandle.Invalid); var handle = _jobScheduler.ScheduleParallel(ref job, _LENGTH, 64, -1, JobHandle.Invalid);
_jobScheduler.WaitComplete(handle); _jobScheduler.Wait(handle);
} }
[Benchmark] [Benchmark]

View File

@@ -37,7 +37,7 @@ public unsafe class SPMDBenchmark
}; };
var handle = _scheduler.ScheduleParallelSPDM<Jobs.NoiseJobMathSPMD, float>(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid); var handle = _scheduler.ScheduleParallelSPDM<Jobs.NoiseJobMathSPMD, float>(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid);
_scheduler.WaitComplete(handle); _scheduler.Wait(handle);
} }
[Benchmark] [Benchmark]
@@ -51,7 +51,7 @@ public unsafe class SPMDBenchmark
}; };
var handle = _scheduler.ScheduleParallelFor(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid); var handle = _scheduler.ScheduleParallelFor(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid);
_scheduler.WaitComplete(handle); _scheduler.Wait(handle);
} }
//[Benchmark] //[Benchmark]
@@ -65,7 +65,7 @@ public unsafe class SPMDBenchmark
}; };
var handle = _scheduler.ScheduleParallel(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid); var handle = _scheduler.ScheduleParallel(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid);
_scheduler.WaitComplete(handle); _scheduler.Wait(handle);
} }
//[Benchmark] //[Benchmark]
@@ -80,7 +80,7 @@ public unsafe class SPMDBenchmark
Parallel.For(0, _SIZE * _SIZE, (i) => Parallel.For(0, _SIZE * _SIZE, (i) =>
{ {
job.Execute(i, 0); job.Execute(i, default);
}); });
} }
@@ -94,6 +94,7 @@ public unsafe class SPMDBenchmark
height = _SIZE, height = _SIZE,
}; };
job.Run(_SIZE * _SIZE, 0); var ctx = new JobExecutionContext(-1, _scheduler);
job.Run(_SIZE * _SIZE, in ctx);
} }
} }

View File

@@ -0,0 +1,35 @@
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Test.Jobs;
internal struct JobDispatchingJob : IJobParallelFor
{
public UnsafeArray<UnsafeArray<int>> data;
public UnsafeList<JobHandle>.ParallelWriter handles;
private struct InnerJob : IJobParallelFor
{
public UnsafeArray<int> data;
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
ref var value = ref data[loopIndex];
value *= 2;
}
}
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{
if (loopIndex % 2 == 0)
{
var innerJob = new InnerJob
{
data = data[loopIndex]
};
var handle = ctx.JobScheduler.ScheduleParallelFor(in innerJob, data[loopIndex].Length, 64, ctx.ThreadIndex);
handles.AddNoResize(handle);
}
}
}

View File

@@ -13,7 +13,7 @@ internal unsafe struct NoiseJobVectorFor : IJobParallelFor
public int width; public int width;
public int height; public int height;
public void Execute(int loopIndex, int threadIndex) public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
{ {
var x = loopIndex % width; var x = loopIndex % width;
var y = loopIndex / height; var y = loopIndex / height;
@@ -65,7 +65,7 @@ internal unsafe struct NoiseJobVector : IJobParallel
return float.Lerp(float.Lerp(d00, d10, fp.Y), float.Lerp(d01, d11, fp.Y), fp.X); return float.Lerp(float.Lerp(d00, d10, fp.Y), float.Lerp(d01, d11, fp.Y), fp.X);
} }
public void Execute(int startIndex, int endIndex, int threadIndex) public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{ {
for (int i = startIndex; i < endIndex; i++) for (int i = startIndex; i < endIndex; i++)
{ {
@@ -118,7 +118,7 @@ internal unsafe struct NoiseJobMath : IJobParallel
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(int startIndex, int endIndex, int threadIndex) public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{ {
for (var i = startIndex; i < endIndex; i++) for (var i = startIndex; i < endIndex; i++)
{ {
@@ -199,7 +199,7 @@ internal unsafe struct NoiseJobMathV : IJobParallel
return lerpY1 + (lerpY2 - lerpY1) * uX; return lerpY1 + (lerpY2 - lerpY1) * uX;
} }
public void Execute(int startIndex, int endIndex, int threadIndex) public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{ {
for (int i = startIndex; i < endIndex; i++) for (int i = startIndex; i < endIndex; i++)
{ {
@@ -291,7 +291,7 @@ internal unsafe struct NoiseJobMathSPMD : IJobSPMD<float>
return T.Lerp(T.Lerp(d00, d10, uY), T.Lerp(d01, d11, uY), uX); return T.Lerp(T.Lerp(d00, d10, uY), T.Lerp(d01, d11, uY), uX);
} }
public readonly void Execute<TLane>(int baseIndex, int threadIndex) public readonly void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, float> where TLane : ISPMD<TLane, float>
{ {
var indices = TLane.Sequence(baseIndex, 1f); var indices = TLane.Sequence(baseIndex, 1f);

View File

@@ -1,6 +1,22 @@
using BenchmarkDotNet.Running; using BenchmarkDotNet.Running;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics.SPMD; using Misaki.HighPerformance.Mathematics.SPMD;
using Misaki.HighPerformance.Test.Benchmark; using Misaki.HighPerformance.Test.Benchmark;
using Misaki.HighPerformance.Test.Jobs; using Misaki.HighPerformance.Test.Jobs;
BenchmarkRunner.Run<SPMDBenchmark>(); //BenchmarkRunner.Run<SPMDBenchmark>();
var hashMap = new UnsafeHashMap<int, int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
hashMap[0] = 5;
hashMap[1] = 6;
Console.WriteLine(hashMap[1]);
ref var v = ref hashMap.GetValueRefOrAddDefault(1, out var exists);
Console.WriteLine(exists);
v = 10;
Console.WriteLine(hashMap[1]);
hashMap.Dispose();

View File

@@ -1,7 +1,8 @@
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.SPMD; using Misaki.HighPerformance.Mathematics.SPMD;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Test.UnitTest.Jobs; namespace Misaki.HighPerformance.Test.UnitTest.Jobs;
@@ -11,7 +12,7 @@ internal unsafe struct DotProductJob : IJobSPMD<float>
public float3* arrayB; // source array 2 public float3* arrayB; // source array 2
public float* results; // output array (dot products) public float* results; // output array (dot products)
public readonly void Execute<TLane>(int baseIndex, int threadIndex) public readonly void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, float> where TLane : ISPMD<TLane, float>
{ {
var vecA = MathV.LoadVector3<TLane, float>((float*)(arrayA + baseIndex)); var vecA = MathV.LoadVector3<TLane, float>((float*)(arrayA + baseIndex));
@@ -28,7 +29,7 @@ internal unsafe struct Vector2LerpJob : IJobSPMD<float>
public float2[] arrayB; public float2[] arrayB;
public float[] results; public float[] results;
public readonly void Execute<TLane>(int baseIndex, int threadIndex) public readonly void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, float> where TLane : ISPMD<TLane, float>
{ {
var a = MathV.LoadVector2<TLane, float>(ref arrayA[baseIndex].x); var a = MathV.LoadVector2<TLane, float>(ref arrayA[baseIndex].x);
@@ -47,7 +48,7 @@ internal unsafe struct Vector4NormalizeJob : IJobSPMD<float>
public float4[] input; public float4[] input;
public float4[] output; public float4[] output;
public readonly void Execute<TLane>(int baseIndex, int threadIndex) public readonly void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, float> where TLane : ISPMD<TLane, float>
{ {
var vec = MathV.LoadVector4<TLane, float>(ref input[baseIndex].x); var vec = MathV.LoadVector4<TLane, float>(ref input[baseIndex].x);
@@ -62,7 +63,7 @@ internal unsafe struct Vector3CrossJob : IJobSPMD<float>
public float3[] arrayB; public float3[] arrayB;
public float3[] results; public float3[] results;
public readonly void Execute<TLane>(int baseIndex, int threadIndex) public readonly void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, float> where TLane : ISPMD<TLane, float>
{ {
var a = MathV.LoadVector3<TLane, float>(ref arrayA[baseIndex].x); var a = MathV.LoadVector3<TLane, float>(ref arrayA[baseIndex].x);
@@ -80,7 +81,7 @@ internal unsafe struct MinMaxClampJob : IJobSPMD<float>
public float3[] maxs; public float3[] maxs;
public float3[] results; public float3[] results;
public readonly void Execute<TLane>(int baseIndex, int threadIndex) public readonly void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, float> where TLane : ISPMD<TLane, float>
{ {
var val = MathV.LoadVector3<TLane, float>(ref values[baseIndex].x); var val = MathV.LoadVector3<TLane, float>(ref values[baseIndex].x);
@@ -98,7 +99,7 @@ internal unsafe struct DistanceJob : IJobSPMD<float>
public float3[] arrayB; public float3[] arrayB;
public float[] results; public float[] results;
public readonly void Execute<TLane>(int baseIndex, int threadIndex) public readonly void Execute<TLane>(int baseIndex, ref readonly JobExecutionContext ctx)
where TLane : ISPMD<TLane, float> where TLane : ISPMD<TLane, float>
{ {
var a = MathV.LoadVector3<TLane, float>(ref arrayA[baseIndex].x); var a = MathV.LoadVector3<TLane, float>(ref arrayA[baseIndex].x);
@@ -134,7 +135,8 @@ public class SPMDTest
results = results results = results
}; };
job.Run<DotProductJob, float>(count, -1);
job.Run<DotProductJob, float>(count, default);
// Verify first result: dot([0,1,2], [1,2,3]) = 0*1 + 1*2 + 2*3 = 8 // Verify first result: dot([0,1,2], [1,2,3]) = 0*1 + 1*2 + 2*3 = 8
Assert.AreEqual(8.0f, results[0], 0.001f); Assert.AreEqual(8.0f, results[0], 0.001f);
@@ -168,7 +170,7 @@ public class SPMDTest
results = results results = results
}; };
job.Run<Vector2LerpJob, float>(count, -1); job.Run<Vector2LerpJob, float>(count, default);
// Verify first result: lerp([0,1], [10,11], 0.5) = [5,6], length = sqrt(25+36) = sqrt(61) // Verify first result: lerp([0,1], [10,11], 0.5) = [5,6], length = sqrt(25+36) = sqrt(61)
var expectedFirst = math.sqrt(5 * 5 + 6 * 6); var expectedFirst = math.sqrt(5 * 5 + 6 * 6);
@@ -198,7 +200,7 @@ public class SPMDTest
output = output output = output
}; };
job.Run<Vector4NormalizeJob, float>(count, -1); job.Run<Vector4NormalizeJob, float>(count, default);
// Verify first result: normalize([1,2,3,4]) // Verify first result: normalize([1,2,3,4])
var len0 = math.sqrt(1 * 1 + 2 * 2 + 3 * 3 + 4 * 4); var len0 = math.sqrt(1 * 1 + 2 * 2 + 3 * 3 + 4 * 4);
@@ -239,7 +241,7 @@ public class SPMDTest
results = results results = results
}; };
job.Run<Vector3CrossJob, float>(count, -1); job.Run<Vector3CrossJob, float>(count, default);
// cross([1,0,0], [0,1,0]) = [0,0,1] // cross([1,0,0], [0,1,0]) = [0,0,1]
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
@@ -275,7 +277,7 @@ public class SPMDTest
results = results results = results
}; };
job.Run<MinMaxClampJob, float>(count, -1); job.Run<MinMaxClampJob, float>(count, default);
// Verify clamping works correctly // Verify clamping works correctly
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
@@ -313,7 +315,7 @@ public class SPMDTest
results = results results = results
}; };
job.Run<DistanceJob, float>(count, -1); job.Run<DistanceJob, float>(count, default);
// distance([0,0,0], [3,4,0]) = 5 // distance([0,0,0], [3,4,0]) = 5
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)

View File

@@ -3,6 +3,7 @@ using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics.SPMD; using Misaki.HighPerformance.Mathematics.SPMD;
using Misaki.HighPerformance.Test.Jobs;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Test.UnitTest.Jobs; namespace Misaki.HighPerformance.Test.UnitTest.Jobs;
@@ -11,7 +12,7 @@ namespace Misaki.HighPerformance.Test.UnitTest.Jobs;
[DoNotParallelize] [DoNotParallelize]
public unsafe class TestJobSystem public unsafe class TestJobSystem
{ {
private JobScheduler _jobScheduler = null!; private static JobScheduler s_jobScheduler = null!;
public TestContext TestContext public TestContext TestContext
{ {
@@ -19,16 +20,17 @@ public unsafe class TestJobSystem
set; set;
} }
[TestInitialize] [ClassInitialize]
public void Initialize() public static void Initialize(TestContext testContext)
{ {
_jobScheduler = new JobScheduler(3); s_jobScheduler = new JobScheduler(3);
} }
[TestCleanup] [ClassCleanup(ClassCleanupBehavior.EndOfClass)]
public void Cleanup() public static void Cleanup()
{ {
_jobScheduler.Dispose(); s_jobScheduler.Dispose();
AllocationManager.Dispose();
} }
[TestMethod] [TestMethod]
@@ -42,8 +44,8 @@ public unsafe class TestJobSystem
result = result result = result
}; };
var handle = _jobScheduler.Schedule(ref job, -1); var handle = s_jobScheduler.Schedule(ref job, -1);
_jobScheduler.WaitComplete(handle); s_jobScheduler.Wait(handle);
Assert.AreEqual(4.0f, *result); Assert.AreEqual(4.0f, *result);
} }
@@ -59,7 +61,7 @@ public unsafe class TestJobSystem
result = result result = result
}; };
var handle1 = _jobScheduler.Schedule(ref job1, -1); var handle1 = s_jobScheduler.Schedule(ref job1, -1);
var job2 = new AddJob var job2 = new AddJob
{ {
@@ -67,8 +69,8 @@ public unsafe class TestJobSystem
result = result result = result
}; };
var handle2 = _jobScheduler.Schedule(ref job2, -1, handle1); var handle2 = s_jobScheduler.Schedule(ref job2, -1, handle1);
_jobScheduler.WaitComplete(handle2); s_jobScheduler.Wait(handle2);
Assert.AreEqual(8.0f, *result); Assert.AreEqual(8.0f, *result);
} }
@@ -84,8 +86,8 @@ public unsafe class TestJobSystem
result = result result = result
}; };
var handle1 = _jobScheduler.Schedule(ref job1); var handle1 = s_jobScheduler.Schedule(ref job1);
_jobScheduler.WaitComplete(handle1); s_jobScheduler.Wait(handle1);
var job2 = new AddJob var job2 = new AddJob
{ {
@@ -93,8 +95,8 @@ public unsafe class TestJobSystem
result = result result = result
}; };
var handle2 = _jobScheduler.Schedule(ref job2, handle1); var handle2 = s_jobScheduler.Schedule(ref job2, handle1);
_jobScheduler.WaitComplete(handle2); s_jobScheduler.Wait(handle2);
Assert.AreEqual(8.0f, *result); Assert.AreEqual(8.0f, *result);
} }
@@ -110,7 +112,7 @@ public unsafe class TestJobSystem
result = result result = result
}; };
var handle1 = _jobScheduler.Schedule(ref job1); var handle1 = s_jobScheduler.Schedule(ref job1);
var job2 = new AddJob var job2 = new AddJob
{ {
@@ -118,7 +120,7 @@ public unsafe class TestJobSystem
result = result result = result
}; };
var handle2 = _jobScheduler.Schedule(ref job2, handle1); var handle2 = s_jobScheduler.Schedule(ref job2, handle1);
var job3 = new AddJob var job3 = new AddJob
{ {
@@ -126,10 +128,10 @@ public unsafe class TestJobSystem
result = result result = result
}; };
var combinedHandle = _jobScheduler.CombineDependencies(handle1, handle2); var combinedHandle = s_jobScheduler.CombineDependencies(handle1, handle2);
var handle3 = _jobScheduler.Schedule(ref job3, combinedHandle); var handle3 = s_jobScheduler.Schedule(ref job3, combinedHandle);
_jobScheduler.WaitComplete(handle3); s_jobScheduler.Wait(handle3);
Assert.AreEqual(19.0f, *result); Assert.AreEqual(19.0f, *result);
} }
@@ -146,8 +148,8 @@ public unsafe class TestJobSystem
inout = result inout = result
}; };
var handle = _jobScheduler.ScheduleParallel(ref job, size, 64); var handle = s_jobScheduler.ScheduleParallel(ref job, size, 64);
_jobScheduler.WaitComplete(handle); s_jobScheduler.Wait(handle);
Assert.AreEqual(1.0f, result[500]); Assert.AreEqual(1.0f, result[500]);
} }
@@ -198,11 +200,11 @@ public unsafe class TestJobSystem
output = result output = result
}; };
var handle1 = _jobScheduler.ScheduleParallel(ref addJob, arraySize, 64); var handle1 = s_jobScheduler.ScheduleParallel(ref addJob, arraySize, 64);
var handle2 = _jobScheduler.ScheduleParallel(ref multiplyJob, arraySize, 64); var handle2 = s_jobScheduler.ScheduleParallel(ref multiplyJob, arraySize, 64);
var handle3 = _jobScheduler.Schedule(ref sumJob, handle2); var handle3 = s_jobScheduler.Schedule(ref sumJob, handle2);
_jobScheduler.WaitComplete(handle3); s_jobScheduler.Wait(handle3);
var expected = ComputeExpectedSum(arraySize); var expected = ComputeExpectedSum(arraySize);
Assert.AreEqual(expected, *result, 0.01f); Assert.AreEqual(expected, *result, 0.01f);
@@ -226,13 +228,13 @@ public unsafe class TestJobSystem
result = result2 result = result2
}; };
var handle1 = _jobScheduler.Schedule(ref job1); var handle1 = s_jobScheduler.Schedule(ref job1);
var handle2 = _jobScheduler.Schedule(ref job2); var handle2 = s_jobScheduler.Schedule(ref job2);
_jobScheduler.WaitAll(handle1, handle2); s_jobScheduler.WaitAll(handle1, handle2);
Assert.AreEqual(JobState.Completed, _jobScheduler.GetJobStatus(handle1)); Assert.AreEqual(JobState.Completed, s_jobScheduler.GetJobStatus(handle1));
Assert.AreEqual(JobState.Completed, _jobScheduler.GetJobStatus(handle2)); Assert.AreEqual(JobState.Completed, s_jobScheduler.GetJobStatus(handle2));
} }
[TestMethod] [TestMethod]
@@ -253,12 +255,12 @@ public unsafe class TestJobSystem
result = result2 result = result2
}; };
var handle1 = _jobScheduler.Schedule(ref job1); var handle1 = s_jobScheduler.Schedule(ref job1);
var handle2 = _jobScheduler.Schedule(ref job2); var handle2 = s_jobScheduler.Schedule(ref job2);
var completedHandle = _jobScheduler.WaitAny(handle1, handle2); var completedHandle = s_jobScheduler.WaitAny(handle1, handle2);
Assert.AreEqual(JobState.Completed, _jobScheduler.GetJobStatus(completedHandle)); Assert.AreEqual(JobState.Completed, s_jobScheduler.GetJobStatus(completedHandle));
} }
[TestMethod] [TestMethod]
@@ -274,7 +276,7 @@ public unsafe class TestJobSystem
// 1. Create a "Gatekeeper" vectorJob that spins/blocks a worker thread until signaled. // 1. Create a "Gatekeeper" vectorJob that spins/blocks a worker thread until signaled.
// This allows us to control exactly when the dependency completes. // This allows us to control exactly when the dependency completes.
var rootJob = new WaitJob { pSignal = &startSignal }; var rootJob = new WaitJob { pSignal = &startSignal };
var rootHandle = _jobScheduler.Schedule(ref rootJob); var rootHandle = s_jobScheduler.Schedule(ref rootJob);
// 2. Start a background task to flood the scheduler with dependencies on the Gatekeeper. // 2. Start a background task to flood the scheduler with dependencies on the Gatekeeper.
using var barrier = new Barrier(2); using var barrier = new Barrier(2);
@@ -288,7 +290,7 @@ public unsafe class TestJobSystem
// CONTENTION POINT: // CONTENTION POINT:
// Trying to add a dependency to 'rootHandle'. // Trying to add a dependency to 'rootHandle'.
// Eventually, this will happen exactly while 'rootHandle' is transitioning to Completed. // Eventually, this will happen exactly while 'rootHandle' is transitioning to Completed.
_jobScheduler.Schedule(ref depJob, rootHandle); s_jobScheduler.Schedule(ref depJob, rootHandle);
} }
}, TestContext.CancellationTokenSource.Token); }, TestContext.CancellationTokenSource.Token);
@@ -321,7 +323,7 @@ public unsafe class TestJobSystem
} }
// Ensure the root vectorJob is officially cleaned up // Ensure the root vectorJob is officially cleaned up
_jobScheduler.WaitComplete(rootHandle); s_jobScheduler.Wait(rootHandle);
Assert.AreEqual(jobCount, *pExecutedCount, "Race condition detected: Some dependent jobs failed to execute (Wait timeout)."); Assert.AreEqual(jobCount, *pExecutedCount, "Race condition detected: Some dependent jobs failed to execute (Wait timeout).");
@@ -335,27 +337,71 @@ public unsafe class TestJobSystem
var vectorBuf = stackalloc float[size * size]; var vectorBuf = stackalloc float[size * size];
var vs = new Span<float>(vectorBuf, size * size); var vs = new Span<float>(vectorBuf, size * size);
var vectorJob = new Misaki.HighPerformance.Test.Jobs.NoiseJobVector var vectorJob = new NoiseJobVector
{ {
buffers = vectorBuf, buffers = vectorBuf,
width = size, width = size,
height = size, height = size,
}; };
vectorJob.Run(size * size, -1); var ctx = new JobExecutionContext(-1, s_jobScheduler);
vectorJob.Run(size * size, in ctx);
var spmdBuf = stackalloc float[size * size]; var spmdBuf = stackalloc float[size * size];
var ss = new Span<float>(spmdBuf, size * size); var ss = new Span<float>(spmdBuf, size * size);
var spmdJob = new Misaki.HighPerformance.Test.Jobs.NoiseJobMath var spmdJob = new NoiseJobMath
{ {
buffers = spmdBuf, buffers = spmdBuf,
width = size, width = size,
height = size, height = size,
}; };
//spmdJob.Run(size * size, -1); spmdJob.Run(size * size, default);
var eq = vs.SequenceCompareTo(ss); var eq = vs.SequenceCompareTo(ss);
Assert.AreEqual(0, eq); Assert.AreEqual(0, eq);
} }
[TestMethod]
public void DynamicDispatch()
{
using var arr = new UnsafeArray<UnsafeArray<int>>(256, Allocator.Persistent);
for (var i = 0; i < arr.Length; i++)
{
arr[i] = new UnsafeArray<int>(256, Allocator.Persistent);
for (var j = 0; j < arr[i].Length; j++)
{
arr[i][j] = j;
}
}
using var handles = new UnsafeList<JobHandle>(arr.Length, Allocator.Persistent);
var job = new JobDispatchingJob
{
data = arr,
handles = handles.AsParallelWriter()
};
var handle = s_jobScheduler.ScheduleParallelFor(ref job, arr.Length, 64);
s_jobScheduler.Wait(handle);
s_jobScheduler.WaitAll(handles.AsSpan());
for (var i = 0; i < arr.Length; i++)
{
if (i % 2 == 0)
{
for (var j = 0; j < arr[i].Length; j++)
{
Assert.AreEqual(j * 2, arr[i][j]);
}
}
}
for (var i = 0; i < arr.Length; i++)
{
arr[i].Dispose();
}
}
} }

View File

@@ -9,7 +9,7 @@ internal unsafe struct TwoSumJob : IJob
public float* result; public float* result;
public void Execute(int threadIndex) public void Execute(ref readonly JobExecutionContext ctx)
{ {
*result = value1 + value2; *result = value1 + value2;
} }
@@ -21,7 +21,7 @@ internal unsafe struct AddJob : IJob
public float* result; public float* result;
public void Execute(int threadIndex) public void Execute(ref readonly JobExecutionContext ctx)
{ {
*result += value; *result += value;
} }
@@ -33,7 +33,7 @@ internal unsafe struct KahanSumJob : IJob
public int length; public int length;
public float* output; public float* output;
public void Execute(int threadIndex) public void Execute(ref readonly JobExecutionContext ctx)
{ {
var sum = 0f; var sum = 0f;
var c = 0f; // Compensation for lost low-order bits var c = 0f; // Compensation for lost low-order bits
@@ -55,7 +55,7 @@ internal unsafe struct ParallelAddJob : IJobParallel
public float value; public float value;
public float* inout; public float* inout;
public void Execute(int startIndex, int endIndex, int threadIndex) public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{ {
for (var i = startIndex; i < endIndex; i++) for (var i = startIndex; i < endIndex; i++)
{ {
@@ -69,7 +69,7 @@ internal unsafe struct ParallelMultiplyJob : IJobParallel
public float multiplier; public float multiplier;
public float* inout; public float* inout;
public void Execute(int startIndex, int endIndex, int threadIndex) public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{ {
for (var i = startIndex; i < endIndex; i++) for (var i = startIndex; i < endIndex; i++)
{ {
@@ -82,7 +82,7 @@ public unsafe struct WaitJob : IJob
{ {
public bool* pSignal; public bool* pSignal;
public void Execute(int loopIndex) public void Execute(ref readonly JobExecutionContext ctx)
{ {
var spin = new SpinWait(); var spin = new SpinWait();
while (!Volatile.Read(ref *pSignal)) while (!Volatile.Read(ref *pSignal))
@@ -96,7 +96,7 @@ public unsafe struct IncrementJob : IJob
{ {
public int* pCounter; public int* pCounter;
public void Execute(int loopIndex) public void Execute(ref readonly JobExecutionContext ctx)
{ {
Interlocked.Increment(ref *pCounter); Interlocked.Increment(ref *pCounter);
} }