From 37d548085ea45d84d61543c040513cc14c676769 Mon Sep 17 00:00:00 2001 From: Misaki Date: Wed, 4 Mar 2026 11:43:39 +0900 Subject: [PATCH] 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. --- .../Misaki.HighPerformance.Jobs.csproj | 2 +- .../contentFiles/cs/any/AssemblyInfo.cs.cs | 4 +- .../contentFiles/cs/any/IJob.cs | 30 ++-- .../cs/any/JobExecutionContext.cs | 20 +++ .../contentFiles/cs/any/JobExecutor.cs | 12 +- .../contentFiles/cs/any/JobScheduler.cs | 37 +++-- .../contentFiles/cs/any/WorkerThread.cs | 12 +- .../Misaki.HighPerformance.LowLevel.csproj | 2 +- .../cs/any/Buffer/AllocationManager.cs | 25 +++- .../cs/any/Collections/HashMapHelper.cs | 89 +++++++++++- .../Collections/ReadOnlyUnsafeCollection.cs | 17 ++- .../cs/any/Collections/UnsafeArray.cs | 17 ++- .../cs/any/Collections/UnsafeHashMap.cs | 55 ++++++-- .../cs/any/Collections/UnsafeHashSet.cs | 32 ++++- .../cs/any/Collections/UnsafeList.cs | 133 ++++++++++++++---- .../cs/any/Collections/UnsafeQueue.cs | 17 ++- .../cs/any/Collections/UnsafeSlotMap.cs | 17 ++- .../cs/any/Collections/UnsafeSparseSet.cs | 17 ++- .../cs/any/Collections/UnsafeStack.cs | 17 ++- .../contentFiles/cs/any/Ptr.cs | 12 +- .../cs/any/Utilities/MemoryUtility.Byte.cs | 20 ++- .../IJobSPMD.cs | 20 +-- ...ki.HighPerformance.Mathematics.SPMD.csproj | 2 +- .../Benchmark/ParallelNoiseBenchmark.cs | 2 +- .../Benchmark/SPMDBenchmark.cs | 11 +- .../Jobs/JobDispatchingJob.cs | 35 +++++ .../Jobs/NoiseJobVector.cs | 10 +- Misaki.HighPerformance.Test/Program.cs | 18 ++- .../UnitTest/Jobs/SPMDTest.cs | 28 ++-- .../UnitTest/Jobs/TestJobSystem.cs | 132 +++++++++++------ .../UnitTest/Jobs/TestJobs.cs | 14 +- 31 files changed, 652 insertions(+), 207 deletions(-) create mode 100644 Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobExecutionContext.cs create mode 100644 Misaki.HighPerformance.Test/Jobs/JobDispatchingJob.cs diff --git a/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj b/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj index 1cf15c0..d2f9b82 100644 --- a/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj +++ b/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj @@ -6,7 +6,7 @@ enable True true - 1.3.1 + 1.5.0 $(AssemblyVersion) Misaki https://git.personalnas.com/Misaki/Misaki.HighPerformance.git diff --git a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/AssemblyInfo.cs.cs b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/AssemblyInfo.cs.cs index bc07b35..3604e5a 100644 --- a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/AssemblyInfo.cs.cs +++ b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/AssemblyInfo.cs.cs @@ -1,2 +1,2 @@ - -global using unsafe JobExecutionFunc = delegate*; \ No newline at end of file + +global using unsafe JobExecutionFunc = delegate*; \ No newline at end of file diff --git a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/IJob.cs b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/IJob.cs index 47613ab..f4d6fd1 100644 --- a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/IJob.cs +++ b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/IJob.cs @@ -8,8 +8,8 @@ public interface IJob /// /// Executes the job logic. /// - /// The index of the thread executing the job, useful for thread-specific operations. - void Execute(int threadIndex); + /// The context of the job execution, providing access to thread-specific information and job scheduling capabilities. + void Execute(ref readonly JobExecutionContext ctx); } /// @@ -21,8 +21,8 @@ public interface IJobParallelFor /// Executes the job for a single item at the given index. /// /// The index of the item to process. - /// The index of the thread executing the job, useful for thread-specific operations. - void Execute(int loopIndex, int threadIndex); + /// The context of the job execution, providing access to thread-specific information and job scheduling capabilities. + void Execute(int loopIndex, ref readonly JobExecutionContext ctx); } /// @@ -35,36 +35,36 @@ public interface IJobParallel /// /// The zero-based index at which to begin the operation. /// The zero-based index at which to end the operation. - /// The index of the thread executing the job, useful for thread-specific operations. - void Execute(int startIndex, int endIndex, int threadIndex); + /// The context of the job execution, providing access to thread-specific information and job scheduling capabilities. + void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx); } public static class IJobExtensions { - public static void Run(this ref T job, int threadIndex) + public static void Run(this ref T job, ref readonly JobExecutionContext ctx) where T : struct, IJob { - job.Execute(threadIndex); + job.Execute(in ctx); } } public static class IJobParallelForExtensions { - public static void Run(this ref T job, int totalIterations, int threadIndex) - where T : struct, IJobParallelFor + public static void Run(this ref T job, int totalIterations, ref readonly JobExecutionContext ctx) + 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 void Run(this ref T job, int totalIterations, int threadIndex) - where T : struct, IJobParallel + public static void Run(this ref T job, int totalIterations, ref readonly JobExecutionContext ctx) + where T : struct, IJobParallel { - job.Execute(0, totalIterations, threadIndex); + job.Execute(0, totalIterations, in ctx); } } \ No newline at end of file diff --git a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobExecutionContext.cs b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobExecutionContext.cs new file mode 100644 index 0000000..1f3943b --- /dev/null +++ b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobExecutionContext.cs @@ -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; + } +} \ No newline at end of file diff --git a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobExecutor.cs b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobExecutor.cs index 6dd77a8..f8ad87f 100644 --- a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobExecutor.cs +++ b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobExecutor.cs @@ -2,11 +2,11 @@ namespace Misaki.HighPerformance.Jobs; internal static unsafe class JobExecutor { - public static bool Execute(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, int threadIndex) + public static bool Execute(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, ref readonly JobExecutionContext ctx) where T : unmanaged, IJob { var pJob = (T*)pJobData; - pJob->Execute(threadIndex); + pJob->Execute(in ctx); return Interlocked.Decrement(ref remainingBatches) == 0; } @@ -25,7 +25,7 @@ internal static unsafe class JobExecutor return true; } - public static bool ExecuteParallelFor(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, int threadIndex) + public static bool ExecuteParallelFor(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, ref readonly JobExecutionContext ctx) where T : unmanaged, IJobParallelFor { var pJob = (T*)pJobData; @@ -40,7 +40,7 @@ internal static unsafe class JobExecutor for (var i = start; i < end; i++) { - pJob->Execute(i, threadIndex); + pJob->Execute(i, in ctx); } if (Interlocked.Decrement(ref remainingBatches) == 0) @@ -52,7 +52,7 @@ internal static unsafe class JobExecutor return wasTheLastBatch; } - public static bool ExecuteParallel(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, int threadIndex) + public static bool ExecuteParallel(void* pJobData, ref JobRanges jobRanges, ref int remainingBatches, ref readonly JobExecutionContext ctx) where T : unmanaged, IJobParallel { var pJob = (T*)pJobData; @@ -65,7 +65,7 @@ internal static unsafe class JobExecutor break; } - pJob->Execute(start, end, threadIndex); + pJob->Execute(start, end, in ctx); if (Interlocked.Decrement(ref remainingBatches) == 0) { wasTheLastBatch = true; diff --git a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobScheduler.cs b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobScheduler.cs index f38e416..9da478b 100644 --- a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobScheduler.cs +++ b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/JobScheduler.cs @@ -1,7 +1,9 @@ using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; using System.Collections.Concurrent; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; namespace Misaki.HighPerformance.Jobs; @@ -189,27 +191,18 @@ public interface IJobScheduler /// Blocks the calling thread until the specified job is completed. /// /// The handle of the job to wait for. - void WaitComplete(JobHandle handle); + void Wait(JobHandle handle); /// /// Blocks the calling thread until all specified job handles have completed. /// - /// This method waits for all jobs referenced by the provided handles to complete before - /// 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. - /// A collection of job handles to wait for. Each handle represents an asynchronous job whose completion is awaited. - /// The collection must not be empty. + /// A collection of job handles to wait for. void WaitAll(params ReadOnlySpan handles); /// /// Waits until any of the specified job handles has completed and returns the first completed handle. /// - /// This method blocks the calling thread until at least one of the specified jobs has finished. - /// 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. - /// A read-only span containing the job handles to monitor for completion. Each handle represents a job whose - /// completion status will be checked. + /// A read-only span containing the job handles to monitor for completion. /// The first job handle from the provided collection that has completed. JobHandle WaitAny(params ReadOnlySpan handles); } @@ -730,7 +723,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable return (JobState)(Volatile.Read(ref Unsafe.As(ref jobInfo.state)) & _STATE_MASK); } - public void WaitComplete(JobHandle handle) + public void Wait(JobHandle handle) { if (!handle.IsValid) { @@ -756,15 +749,29 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable public void WaitAll(params ReadOnlySpan handles) { + if (handles.Length == 0) + { + return; + } + + using var orderedHandles = new UnsafeArray(handles.Length, Allocator.Temp); var spin = new SpinWait(); + var completedCount = 0; + + orderedHandles.CopyFrom(handles); while (true) { - var completedCount = 0; - foreach (var handle in handles) + for (int i = completedCount; i < orderedHandles.Length; i++) { + var handle = orderedHandles[i]; 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++; } } diff --git a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/WorkerThread.cs b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/WorkerThread.cs index d946168..6671ae4 100644 --- a/Misaki.HighPerformance.Jobs/contentFiles/cs/any/WorkerThread.cs +++ b/Misaki.HighPerformance.Jobs/contentFiles/cs/any/WorkerThread.cs @@ -103,11 +103,17 @@ internal class WorkerThread : IDisposable ref var jobInfo = ref _scheduler.GetJobInfoReference(handle, out var exist); if (exist && Interlocked.CompareExchange(ref jobInfo.state, JobState.Running, JobState.Scheduled) == JobState.Scheduled) { - if (jobInfo.pExecutionFunc == null - || jobInfo.pExecutionFunc(jobInfo.pJobData, ref jobInfo.jobRanges, ref jobInfo.remainingBatches, _index)) + if (jobInfo.pExecutionFunc != null) { - _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); } } } diff --git a/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj b/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj index a289b17..8daf062 100644 --- a/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj +++ b/Misaki.HighPerformance.LowLevel/Misaki.HighPerformance.LowLevel.csproj @@ -7,7 +7,7 @@ true true Misaki - 1.3.9 + 1.4.0 $(AssemblyVersion) https://git.personalnas.com/Misaki/Misaki.HighPerformance.git https://git.personalnas.com/Misaki/Misaki.HighPerformance.git diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Buffer/AllocationManager.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Buffer/AllocationManager.cs index 1ec60f3..3b49915 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Buffer/AllocationManager.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Buffer/AllocationManager.cs @@ -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; @@ -257,6 +257,11 @@ public static unsafe class AllocationManager { return s_stack.CreateScope(pSelf->_handle); } + + public readonly void Dispose() + { + Stack.DisposeAll(); + } } private const uint _DEFAULT_MEMORY_POOL_SIZE = 1024 * 1024; // 1 MB @@ -313,10 +318,16 @@ public static unsafe class AllocationManager } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static GCHandle HeaderGetHandle(AllocationHeader* header) => header->stackHandle; + private static GCHandle HeaderGetHandle(AllocationHeader* header) + { + return header->stackHandle; + } [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)] private static void HeaderFreeHandle(AllocationHeader* header) @@ -691,9 +702,10 @@ public static unsafe class AllocationManager { 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."); } @@ -702,8 +714,7 @@ public static unsafe class AllocationManager if (s_pArenaAllocator != null) { s_pArenaAllocator->Dispose(); - - Stack.DisposeAll(); + s_pStackAllocator->Dispose(); NativeMemory.Free(s_pArenaAllocator); } diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/HashMapHelper.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/HashMapHelper.cs index ac3c96f..3f45dd2 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/HashMapHelper.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/HashMapHelper.cs @@ -9,7 +9,7 @@ namespace Misaki.HighPerformance.LowLevel.Collections; public unsafe struct HashMapHelper : IDisposable where TKey : unmanaged, IEquatable { - internal unsafe struct Enumerator + internal struct Enumerator { public HashMapHelper* buffer; public int index; @@ -192,11 +192,17 @@ public unsafe struct HashMapHelper : IDisposable return result; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private readonly int GetBucket(int hash) + { + return hash & (_bucketCapacity - 1); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private readonly int GetBucket(in TKey key) { - var h = (uint)key.GetHashCode(); - return (int)(h & (uint)(_bucketCapacity - 1)); + var h = key.GetHashCode(); + return GetBucket(h); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -431,6 +437,7 @@ public unsafe struct HashMapHelper : IDisposable where TValue : unmanaged { ThrowIfNotCreated(); + var idx = Find(key); if (idx != -1) { @@ -442,6 +449,82 @@ public unsafe struct HashMapHelper : IDisposable return ref Unsafe.NullRef(); } + public ref TValue GetValueRefOrAddDefault(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(_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(_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(_buffer, idx); + } + public bool MoveNextSearch(ref int bucketIndex, ref int nextIndex, out int index) { ThrowIfNotCreated(); diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/ReadOnlyUnsafeCollection.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/ReadOnlyUnsafeCollection.cs index 9a1937d..65d0e2f 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/ReadOnlyUnsafeCollection.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/ReadOnlyUnsafeCollection.cs @@ -73,9 +73,20 @@ public readonly unsafe struct ReadOnlyUnsafeCollection : IEnumerable } } - public Enumerator GetEnumerator() => new Enumerator(in this); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new Enumerator(in this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } public ReadOnlyUnsafeCollection(T* buffer, int count) { diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeArray.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeArray.cs index e69daee..82fe3f3 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeArray.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeArray.cs @@ -122,9 +122,20 @@ public unsafe struct UnsafeArray : IUnsafeCollection } } - public Enumerator GetEnumerator() => new((UnsafeArray*)UnsafeUtility.AddressOf(ref this)); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new((UnsafeArray*)UnsafeUtility.AddressOf(ref this)); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } /// /// Invalid constructor, use or instead. diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeHashMap.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeHashMap.cs index 08cd602..f21bcd7 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeHashMap.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeHashMap.cs @@ -7,7 +7,8 @@ using System.Runtime.CompilerServices; namespace Misaki.HighPerformance.LowLevel.Collections; public unsafe struct UnsafeHashMap : IUnsafeHashCollection> - where TKey : unmanaged, IEquatable where TValue : unmanaged + where TKey : unmanaged, IEquatable + where TValue : unmanaged { public struct Enumerator : IEnumerator> { @@ -22,9 +23,15 @@ public unsafe struct UnsafeHashMap : IUnsafeHashCollection _enumerator.MoveNext(); + public bool MoveNext() + { + return _enumerator.MoveNext(); + } - public void Reset() => _enumerator.Reset(); + public void Reset() + { + _enumerator.Reset(); + } public void Dispose() { @@ -68,9 +75,20 @@ public unsafe struct UnsafeHashMap : IUnsafeHashCollection new((HashMapHelper*)UnsafeUtility.AddressOf(ref this)); - IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new((HashMapHelper*)UnsafeUtility.AddressOf(ref this)); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } /// /// Invalid constructor, use or instead. @@ -167,6 +185,11 @@ public unsafe struct UnsafeHashMap : IUnsafeHashCollection(key, out exists); } + public ref TValue GetValueRefOrAddDefault(in TKey key, out bool exists) + { + return ref _helper.GetValueRefOrAddDefault(key, out exists); + } + /// /// Returns true if a given key is present in this hash map. /// @@ -180,7 +203,10 @@ public unsafe struct UnsafeHashMap : IUnsafeHashCollection /// Sets the capacity to match what it would be if it had been originally initialized with all its entries. /// - public void TrimExcess() => _helper.TrimExcess(); + public void TrimExcess() + { + _helper.TrimExcess(); + } public void Resize(int newSize, AllocationOption option = AllocationOption.None) { @@ -196,20 +222,29 @@ public unsafe struct UnsafeHashMap : IUnsafeHashCollection /// An array containing the keys stored in the hash map. - public UnsafeArray GetKeyArray(Allocator allocator) => _helper.GetKeyArray(allocator); + public UnsafeArray GetKeyArray(Allocator allocator) + { + return _helper.GetKeyArray(allocator); + } /// /// Retrieves an array of values from the underlying hash map. /// /// An UnsafeArray containing the values stored in the hash map. - public UnsafeArray GetValueArray(Allocator allocator) => _helper.GetValueArray(allocator); + public UnsafeArray GetValueArray(Allocator allocator) + { + return _helper.GetValueArray(allocator); + } /// /// Retrieves an array of key-value pairs from the hash map. The keys are of type TKey and the values are of type /// TValue. /// /// Returns an UnsafeArray containing KeyValuePair objects. - public UnsafeArray> GetKeyValueArrays(Allocator allocator) => _helper.GetKeyValueArrays(allocator); + public UnsafeArray> GetKeyValueArrays(Allocator allocator) + { + return _helper.GetKeyValueArrays(allocator); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void* GetUnsafePtr() diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeHashSet.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeHashSet.cs index 1f6d3bd..d483366 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeHashSet.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeHashSet.cs @@ -27,9 +27,15 @@ public unsafe struct UnsafeHashSet : IUnsafeHashCollection, IEnumerable } [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() { @@ -42,9 +48,20 @@ public unsafe struct UnsafeHashSet : IUnsafeHashCollection, IEnumerable public readonly int Capacity => _helper.Capacity; public readonly bool IsCreated => _helper.IsCreated; - public Enumerator GetEnumerator() => new((HashMapHelper*)UnsafeUtility.AddressOf(ref this)); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new((HashMapHelper*)UnsafeUtility.AddressOf(ref this)); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } /// /// Invalid constructor. Use or instead."/> @@ -97,7 +114,10 @@ public unsafe struct UnsafeHashSet : IUnsafeHashCollection, IEnumerable /// /// Sets the capacity to match what it would be if it had been originally initialized with all its entries. /// - public void TrimExcess() => _helper.TrimExcess(); + public void TrimExcess() + { + _helper.TrimExcess(); + } /// /// Returns an array with a copy of this set's values (in no particular order). diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeList.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeList.cs index 4b8a5c5..0b96cac 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeList.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeList.cs @@ -22,7 +22,7 @@ internal class UnsafeListDebugView get { 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]; } @@ -71,20 +71,56 @@ public unsafe struct UnsafeList : IUnsafeCollection } } + /// + /// A Parallel reader for an UnsafeList. + /// + /// + /// Use 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. + /// + public readonly unsafe struct ParallelReader + { + public readonly UnsafeList* 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* list) + { + listData = list; + } + + public readonly Enumerator GetEnumerator() + { + return new Enumerator(listData); + } + + public readonly ReadOnlySpan AsSpan() + { + return new ReadOnlySpan(listData->_array.GetUnsafePtr(), listData->_count); + } + } + /// /// A parallel writer for an UnsafeList. /// /// /// Use 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. /// - public unsafe struct ParallelWriter + public readonly unsafe struct ParallelWriter { - private volatile int _resizeLock; - - /// - /// The UnsafeList to write to. - /// - public UnsafeList* listData; + public readonly UnsafeList* listData; internal ParallelWriter(UnsafeList* list) { @@ -139,23 +175,6 @@ public unsafe struct UnsafeList : IUnsafeCollection get => ref _array[index]; } - public Enumerator GetEnumerator() => new ((UnsafeList*)UnsafeUtility.AddressOf(ref this)); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - /// Provides a parallel writer for the current list, enabling thread-safe additions to the list. - /// - /// A instance that can be used to add items to the list in a thread-safe manner. - public ParallelWriter AsParallelWriter() => new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); - - /// - /// Converts the current list to an UnsafeArray representation. - /// - /// A new instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly UnsafeArray AsUnsafeArray() => new((T*)_array.GetUnsafePtr(), _count); - /// /// Invalid constructor, use or instead. /// @@ -196,9 +215,7 @@ public unsafe struct UnsafeList : IUnsafeCollection { if (index + count > Capacity) { - throw new Exception( - $"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Count}), requested count {count}!" - ); + throw new Exception($"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Count}), requested count {count}!"); } } @@ -225,6 +242,66 @@ public unsafe struct UnsafeList : IUnsafeCollection } } + public Enumerator GetEnumerator() + { + return new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Provides a parallel reader for the current list, enabling thread-safe read operations. + /// + /// + /// 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. + /// + /// A instance that can be used to read items from the list in a thread-safe manner. + public ParallelReader AsParallelReader() + { + return new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); + } + + /// + /// Provides a parallel writer for the current list, enabling thread-safe additions to the 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. + /// 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. + /// + /// A instance that can be used to add items to the list in a thread-safe manner. + public ParallelWriter AsParallelWriter() + { + return new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); + } + + /// + /// Converts the current list to an UnsafeArray representation. + /// + /// + /// The returned shares the same underlying data as the list and does not own the memory. + /// + /// A new instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly UnsafeArray AsUnsafeArray() + { + return new UnsafeArray((T*)_array.GetUnsafePtr(), _count); + } + + /// + /// Converts the current list to a read-only collection that provides unsafe access to its elements. + /// + /// A new instance that allows for read-only access to the list's elements without copying. public readonly ReadOnlyUnsafeCollection AsReadOnly() { return new ReadOnlyUnsafeCollection((T*)_array.GetUnsafePtr(), _count); diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeQueue.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeQueue.cs index 7f25dbe..7cf6596 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeQueue.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeQueue.cs @@ -62,9 +62,20 @@ public unsafe struct UnsafeQueue : IUnsafeCollection set => _array[index] = value; } - public Enumerator GetEnumerator() => new((UnsafeQueue*)UnsafeUtility.AddressOf(ref this)); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new((UnsafeQueue*)UnsafeUtility.AddressOf(ref this)); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } /// /// Invalid constructor. Use or instead."/> diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeSlotMap.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeSlotMap.cs index c8a11d9..f54717f 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeSlotMap.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeSlotMap.cs @@ -86,9 +86,20 @@ public unsafe struct UnsafeSlotMap : IUnsafeCollection public readonly bool IsCreated => _data.IsCreated && _generations.IsCreated && _freeSlots.IsCreated && _validBits.IsCreated; - public Enumerator GetEnumerator() => new((UnsafeSlotMap*)UnsafeUtility.AddressOf(ref this)); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new((UnsafeSlotMap*)UnsafeUtility.AddressOf(ref this)); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } /// /// Invalid constructor. Use or instead."/> diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeSparseSet.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeSparseSet.cs index 282db52..557dad1 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeSparseSet.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeSparseSet.cs @@ -89,9 +89,20 @@ public unsafe struct UnsafeSparseSet : IUnsafeCollection public readonly int Capacity => _capacity; public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated; - public Enumerator GetEnumerator() => new((UnsafeSparseSet*)UnsafeUtility.AddressOf(ref this)); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new((UnsafeSparseSet*)UnsafeUtility.AddressOf(ref this)); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } /// /// Constructs an UnsafeSparseSet with a default size of 1 and uses the Persistent allocator. diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeStack.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeStack.cs index 1c51f40..4931240 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeStack.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Collections/UnsafeStack.cs @@ -81,9 +81,20 @@ public unsafe struct UnsafeStack : IUnsafeCollection public readonly int Capacity => _array.Count; public readonly bool IsCreated => _array.IsCreated; - public Enumerator GetEnumerator() => new((UnsafeStack*)UnsafeUtility.AddressOf(ref this)); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public Enumerator GetEnumerator() + { + return new((UnsafeStack*)UnsafeUtility.AddressOf(ref this)); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } /// /// Invalid constructor, use or instead. diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Ptr.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Ptr.cs index 4750cff..4c6628f 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Ptr.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Ptr.cs @@ -24,6 +24,11 @@ public readonly unsafe struct SharedPtr : IEquatable> return _value; } + public ref T GetRef() + { + return ref *_value; + } + public bool Equals(SharedPtr other) { return _value == other._value; @@ -81,11 +86,16 @@ public unsafe struct UniquePtr : IEquatable> _value = value; } - public readonly T* Get() + public T* Get() { return _value; } + public ref T GetRef() + { + return ref *_value; + } + public readonly SharedPtr Share() { return new SharedPtr(_value); diff --git a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Utilities/MemoryUtility.Byte.cs b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Utilities/MemoryUtility.Byte.cs index d0f252a..3fd4d11 100644 --- a/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Utilities/MemoryUtility.Byte.cs +++ b/Misaki.HighPerformance.LowLevel/contentFiles/cs/any/Utilities/MemoryUtility.Byte.cs @@ -15,23 +15,33 @@ public static unsafe partial class MemoryUtility [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector128 LoadVector128(ref byte start, nuint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref start, offset)); + { + return Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref start, offset)); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector256 LoadVector256(ref byte start, nuint offset) - => Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref start, offset)); + { + return Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref start, offset)); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint GetByteVector128SpanLength(nuint offset, int length) - => (uint)((length - (int)offset) & ~(Vector128.Count - 1)); + { + return (uint)((length - (int)offset) & ~(Vector128.Count - 1)); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint GetByteVector256SpanLength(nuint offset, int length) - => (uint)((length - (int)offset) & ~(Vector256.Count - 1)); + { + return (uint)((length - (int)offset) & ~(Vector256.Count - 1)); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint GetByteVector512SpanLength(nuint offset, int length) - => (uint)((length - (int)offset) & ~(Vector512.Count - 1)); + { + return (uint)((length - (int)offset) & ~(Vector512.Count - 1)); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe nuint UnalignedCountVector128(byte* searchSpace) diff --git a/Misaki.HighPerformance.Mathematics.SPMD/IJobSPMD.cs b/Misaki.HighPerformance.Mathematics.SPMD/IJobSPMD.cs index 1527122..3540677 100644 --- a/Misaki.HighPerformance.Mathematics.SPMD/IJobSPMD.cs +++ b/Misaki.HighPerformance.Mathematics.SPMD/IJobSPMD.cs @@ -6,7 +6,7 @@ namespace Misaki.HighPerformance.Mathematics.SPMD; public interface IJobSPMD where TNumber : unmanaged, INumber, IBinaryNumber, IMinMaxValue, IBitwiseOperators { - void Execute(int baseIndex, int threadIndex) + void Execute(int baseIndex, ref readonly JobExecutionContext ctx) where TLane : ISPMD; } @@ -17,20 +17,20 @@ internal struct SPMDJobWrapper : IJobParallelFor public T innerJob; public int totalCount; - public void Execute(int loopIndex, int threadIndex) + public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) { var baseIndex = loopIndex * WideLane.LaneWidth; var remaining = totalCount - baseIndex; if (remaining >= WideLane.LaneWidth) { - innerJob.Execute>(baseIndex, threadIndex); + innerJob.Execute>(baseIndex, in ctx); } else { for (var j = 0; j < remaining; j++) { - innerJob.Execute>(baseIndex + j, threadIndex); + innerJob.Execute>(baseIndex + j, in ctx); } } } @@ -43,15 +43,15 @@ internal struct SPMDScalerJobWrapper : IJobParallelFor public T innerJob; public int totalCount; - public void Execute(int loopIndex, int threadIndex) + public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) { - innerJob.Execute>(loopIndex, threadIndex); + innerJob.Execute>(loopIndex, in ctx); } } public static class IJobParallelForSPMDExtensions { - public static void Run(this ref T job, int totalCount, int threadIndex) + public static void Run(this ref T job, int totalCount, ref readonly JobExecutionContext ctx) where T : struct, IJobSPMD where TNumber : unmanaged, INumber, IBinaryNumber, IMinMaxValue, IBitwiseOperators { @@ -65,13 +65,13 @@ public static class IJobParallelForSPMDExtensions if (remaining >= WideLane.LaneWidth) { - job.Execute>(baseIndex, threadIndex); + job.Execute>(baseIndex, in ctx); } else { for (var i = 0; i < remaining; i++) { - job.Execute>(baseIndex + i, threadIndex); + job.Execute>(baseIndex + i, in ctx); } } } @@ -80,7 +80,7 @@ public static class IJobParallelForSPMDExtensions { for (var loopIndex = 0; loopIndex < totalCount; loopIndex++) { - job.Execute>(loopIndex, threadIndex); + job.Execute>(loopIndex, in ctx); } } } diff --git a/Misaki.HighPerformance.Mathematics.SPMD/Misaki.HighPerformance.Mathematics.SPMD.csproj b/Misaki.HighPerformance.Mathematics.SPMD/Misaki.HighPerformance.Mathematics.SPMD.csproj index 47122ec..7ff03a4 100644 --- a/Misaki.HighPerformance.Mathematics.SPMD/Misaki.HighPerformance.Mathematics.SPMD.csproj +++ b/Misaki.HighPerformance.Mathematics.SPMD/Misaki.HighPerformance.Mathematics.SPMD.csproj @@ -7,7 +7,7 @@ true true Misaki - 1.0.0 + 1.1.0 $(AssemblyVersion) https://git.personalnas.com/Misaki/Misaki.HighPerformance.git https://git.personalnas.com/Misaki/Misaki.HighPerformance.git diff --git a/Misaki.HighPerformance.Test/Benchmark/ParallelNoiseBenchmark.cs b/Misaki.HighPerformance.Test/Benchmark/ParallelNoiseBenchmark.cs index c978a24..3d5e2d4 100644 --- a/Misaki.HighPerformance.Test/Benchmark/ParallelNoiseBenchmark.cs +++ b/Misaki.HighPerformance.Test/Benchmark/ParallelNoiseBenchmark.cs @@ -42,7 +42,7 @@ public class ParallelNoiseBenchmark }; var handle = _jobScheduler.ScheduleParallel(ref job, _LENGTH, 64, -1, JobHandle.Invalid); - _jobScheduler.WaitComplete(handle); + _jobScheduler.Wait(handle); } [Benchmark] diff --git a/Misaki.HighPerformance.Test/Benchmark/SPMDBenchmark.cs b/Misaki.HighPerformance.Test/Benchmark/SPMDBenchmark.cs index 23fba31..7631b9f 100644 --- a/Misaki.HighPerformance.Test/Benchmark/SPMDBenchmark.cs +++ b/Misaki.HighPerformance.Test/Benchmark/SPMDBenchmark.cs @@ -37,7 +37,7 @@ public unsafe class SPMDBenchmark }; var handle = _scheduler.ScheduleParallelSPDM(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid); - _scheduler.WaitComplete(handle); + _scheduler.Wait(handle); } [Benchmark] @@ -51,7 +51,7 @@ public unsafe class SPMDBenchmark }; var handle = _scheduler.ScheduleParallelFor(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid); - _scheduler.WaitComplete(handle); + _scheduler.Wait(handle); } //[Benchmark] @@ -65,7 +65,7 @@ public unsafe class SPMDBenchmark }; var handle = _scheduler.ScheduleParallel(ref job, _SIZE * _SIZE, 64, -1, JobHandle.Invalid); - _scheduler.WaitComplete(handle); + _scheduler.Wait(handle); } //[Benchmark] @@ -80,7 +80,7 @@ public unsafe class SPMDBenchmark Parallel.For(0, _SIZE * _SIZE, (i) => { - job.Execute(i, 0); + job.Execute(i, default); }); } @@ -94,6 +94,7 @@ public unsafe class SPMDBenchmark height = _SIZE, }; - job.Run(_SIZE * _SIZE, 0); + var ctx = new JobExecutionContext(-1, _scheduler); + job.Run(_SIZE * _SIZE, in ctx); } } diff --git a/Misaki.HighPerformance.Test/Jobs/JobDispatchingJob.cs b/Misaki.HighPerformance.Test/Jobs/JobDispatchingJob.cs new file mode 100644 index 0000000..daa49e5 --- /dev/null +++ b/Misaki.HighPerformance.Test/Jobs/JobDispatchingJob.cs @@ -0,0 +1,35 @@ +using Misaki.HighPerformance.Jobs; +using Misaki.HighPerformance.LowLevel.Collections; + +namespace Misaki.HighPerformance.Test.Jobs; + +internal struct JobDispatchingJob : IJobParallelFor +{ + public UnsafeArray> data; + public UnsafeList.ParallelWriter handles; + + private struct InnerJob : IJobParallelFor + { + public UnsafeArray 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); + } + } +} \ No newline at end of file diff --git a/Misaki.HighPerformance.Test/Jobs/NoiseJobVector.cs b/Misaki.HighPerformance.Test/Jobs/NoiseJobVector.cs index 329bd1b..db5c0dd 100644 --- a/Misaki.HighPerformance.Test/Jobs/NoiseJobVector.cs +++ b/Misaki.HighPerformance.Test/Jobs/NoiseJobVector.cs @@ -13,7 +13,7 @@ internal unsafe struct NoiseJobVectorFor : IJobParallelFor public int width; public int height; - public void Execute(int loopIndex, int threadIndex) + public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) { var x = loopIndex % width; 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); } - 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++) { @@ -118,7 +118,7 @@ internal unsafe struct NoiseJobMath : IJobParallel } [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++) { @@ -199,7 +199,7 @@ internal unsafe struct NoiseJobMathV : IJobParallel 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++) { @@ -291,7 +291,7 @@ internal unsafe struct NoiseJobMathSPMD : IJobSPMD return T.Lerp(T.Lerp(d00, d10, uY), T.Lerp(d01, d11, uY), uX); } - public readonly void Execute(int baseIndex, int threadIndex) + public readonly void Execute(int baseIndex, ref readonly JobExecutionContext ctx) where TLane : ISPMD { var indices = TLane.Sequence(baseIndex, 1f); diff --git a/Misaki.HighPerformance.Test/Program.cs b/Misaki.HighPerformance.Test/Program.cs index edce38e..cb8e681 100644 --- a/Misaki.HighPerformance.Test/Program.cs +++ b/Misaki.HighPerformance.Test/Program.cs @@ -1,6 +1,22 @@ using BenchmarkDotNet.Running; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.Mathematics.SPMD; using Misaki.HighPerformance.Test.Benchmark; using Misaki.HighPerformance.Test.Jobs; -BenchmarkRunner.Run(); +//BenchmarkRunner.Run(); +var hashMap = new UnsafeHashMap(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(); \ No newline at end of file diff --git a/Misaki.HighPerformance.Test/UnitTest/Jobs/SPMDTest.cs b/Misaki.HighPerformance.Test/UnitTest/Jobs/SPMDTest.cs index 4743c66..41e3eed 100644 --- a/Misaki.HighPerformance.Test/UnitTest/Jobs/SPMDTest.cs +++ b/Misaki.HighPerformance.Test/UnitTest/Jobs/SPMDTest.cs @@ -1,7 +1,8 @@ +using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics.SPMD; -using System.Runtime.InteropServices; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Misaki.HighPerformance.Test.UnitTest.Jobs; @@ -11,7 +12,7 @@ internal unsafe struct DotProductJob : IJobSPMD public float3* arrayB; // source array 2 public float* results; // output array (dot products) - public readonly void Execute(int baseIndex, int threadIndex) + public readonly void Execute(int baseIndex, ref readonly JobExecutionContext ctx) where TLane : ISPMD { var vecA = MathV.LoadVector3((float*)(arrayA + baseIndex)); @@ -28,7 +29,7 @@ internal unsafe struct Vector2LerpJob : IJobSPMD public float2[] arrayB; public float[] results; - public readonly void Execute(int baseIndex, int threadIndex) + public readonly void Execute(int baseIndex, ref readonly JobExecutionContext ctx) where TLane : ISPMD { var a = MathV.LoadVector2(ref arrayA[baseIndex].x); @@ -47,7 +48,7 @@ internal unsafe struct Vector4NormalizeJob : IJobSPMD public float4[] input; public float4[] output; - public readonly void Execute(int baseIndex, int threadIndex) + public readonly void Execute(int baseIndex, ref readonly JobExecutionContext ctx) where TLane : ISPMD { var vec = MathV.LoadVector4(ref input[baseIndex].x); @@ -62,7 +63,7 @@ internal unsafe struct Vector3CrossJob : IJobSPMD public float3[] arrayB; public float3[] results; - public readonly void Execute(int baseIndex, int threadIndex) + public readonly void Execute(int baseIndex, ref readonly JobExecutionContext ctx) where TLane : ISPMD { var a = MathV.LoadVector3(ref arrayA[baseIndex].x); @@ -80,7 +81,7 @@ internal unsafe struct MinMaxClampJob : IJobSPMD public float3[] maxs; public float3[] results; - public readonly void Execute(int baseIndex, int threadIndex) + public readonly void Execute(int baseIndex, ref readonly JobExecutionContext ctx) where TLane : ISPMD { var val = MathV.LoadVector3(ref values[baseIndex].x); @@ -98,7 +99,7 @@ internal unsafe struct DistanceJob : IJobSPMD public float3[] arrayB; public float[] results; - public readonly void Execute(int baseIndex, int threadIndex) + public readonly void Execute(int baseIndex, ref readonly JobExecutionContext ctx) where TLane : ISPMD { var a = MathV.LoadVector3(ref arrayA[baseIndex].x); @@ -134,7 +135,8 @@ public class SPMDTest results = results }; - job.Run(count, -1); + + job.Run(count, default); // 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); @@ -168,7 +170,7 @@ public class SPMDTest results = results }; - job.Run(count, -1); + job.Run(count, default); // 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); @@ -198,7 +200,7 @@ public class SPMDTest output = output }; - job.Run(count, -1); + job.Run(count, default); // Verify first result: normalize([1,2,3,4]) var len0 = math.sqrt(1 * 1 + 2 * 2 + 3 * 3 + 4 * 4); @@ -239,7 +241,7 @@ public class SPMDTest results = results }; - job.Run(count, -1); + job.Run(count, default); // cross([1,0,0], [0,1,0]) = [0,0,1] for (var i = 0; i < count; i++) @@ -275,7 +277,7 @@ public class SPMDTest results = results }; - job.Run(count, -1); + job.Run(count, default); // Verify clamping works correctly for (var i = 0; i < count; i++) @@ -313,7 +315,7 @@ public class SPMDTest results = results }; - job.Run(count, -1); + job.Run(count, default); // distance([0,0,0], [3,4,0]) = 5 for (var i = 0; i < count; i++) diff --git a/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobSystem.cs b/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobSystem.cs index d0d1458..cc1970e 100644 --- a/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobSystem.cs +++ b/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobSystem.cs @@ -3,6 +3,7 @@ using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.Mathematics.SPMD; +using Misaki.HighPerformance.Test.Jobs; using System.Runtime.InteropServices; namespace Misaki.HighPerformance.Test.UnitTest.Jobs; @@ -11,7 +12,7 @@ namespace Misaki.HighPerformance.Test.UnitTest.Jobs; [DoNotParallelize] public unsafe class TestJobSystem { - private JobScheduler _jobScheduler = null!; + private static JobScheduler s_jobScheduler = null!; public TestContext TestContext { @@ -19,16 +20,17 @@ public unsafe class TestJobSystem set; } - [TestInitialize] - public void Initialize() + [ClassInitialize] + public static void Initialize(TestContext testContext) { - _jobScheduler = new JobScheduler(3); + s_jobScheduler = new JobScheduler(3); } - [TestCleanup] - public void Cleanup() + [ClassCleanup(ClassCleanupBehavior.EndOfClass)] + public static void Cleanup() { - _jobScheduler.Dispose(); + s_jobScheduler.Dispose(); + AllocationManager.Dispose(); } [TestMethod] @@ -42,8 +44,8 @@ public unsafe class TestJobSystem result = result }; - var handle = _jobScheduler.Schedule(ref job, -1); - _jobScheduler.WaitComplete(handle); + var handle = s_jobScheduler.Schedule(ref job, -1); + s_jobScheduler.Wait(handle); Assert.AreEqual(4.0f, *result); } @@ -59,7 +61,7 @@ public unsafe class TestJobSystem result = result }; - var handle1 = _jobScheduler.Schedule(ref job1, -1); + var handle1 = s_jobScheduler.Schedule(ref job1, -1); var job2 = new AddJob { @@ -67,8 +69,8 @@ public unsafe class TestJobSystem result = result }; - var handle2 = _jobScheduler.Schedule(ref job2, -1, handle1); - _jobScheduler.WaitComplete(handle2); + var handle2 = s_jobScheduler.Schedule(ref job2, -1, handle1); + s_jobScheduler.Wait(handle2); Assert.AreEqual(8.0f, *result); } @@ -84,8 +86,8 @@ public unsafe class TestJobSystem result = result }; - var handle1 = _jobScheduler.Schedule(ref job1); - _jobScheduler.WaitComplete(handle1); + var handle1 = s_jobScheduler.Schedule(ref job1); + s_jobScheduler.Wait(handle1); var job2 = new AddJob { @@ -93,8 +95,8 @@ public unsafe class TestJobSystem result = result }; - var handle2 = _jobScheduler.Schedule(ref job2, handle1); - _jobScheduler.WaitComplete(handle2); + var handle2 = s_jobScheduler.Schedule(ref job2, handle1); + s_jobScheduler.Wait(handle2); Assert.AreEqual(8.0f, *result); } @@ -110,7 +112,7 @@ public unsafe class TestJobSystem result = result }; - var handle1 = _jobScheduler.Schedule(ref job1); + var handle1 = s_jobScheduler.Schedule(ref job1); var job2 = new AddJob { @@ -118,7 +120,7 @@ public unsafe class TestJobSystem result = result }; - var handle2 = _jobScheduler.Schedule(ref job2, handle1); + var handle2 = s_jobScheduler.Schedule(ref job2, handle1); var job3 = new AddJob { @@ -126,10 +128,10 @@ public unsafe class TestJobSystem result = result }; - var combinedHandle = _jobScheduler.CombineDependencies(handle1, handle2); - var handle3 = _jobScheduler.Schedule(ref job3, combinedHandle); + var combinedHandle = s_jobScheduler.CombineDependencies(handle1, handle2); + var handle3 = s_jobScheduler.Schedule(ref job3, combinedHandle); - _jobScheduler.WaitComplete(handle3); + s_jobScheduler.Wait(handle3); Assert.AreEqual(19.0f, *result); } @@ -146,8 +148,8 @@ public unsafe class TestJobSystem inout = result }; - var handle = _jobScheduler.ScheduleParallel(ref job, size, 64); - _jobScheduler.WaitComplete(handle); + var handle = s_jobScheduler.ScheduleParallel(ref job, size, 64); + s_jobScheduler.Wait(handle); Assert.AreEqual(1.0f, result[500]); } @@ -198,11 +200,11 @@ public unsafe class TestJobSystem output = result }; - var handle1 = _jobScheduler.ScheduleParallel(ref addJob, arraySize, 64); - var handle2 = _jobScheduler.ScheduleParallel(ref multiplyJob, arraySize, 64); - var handle3 = _jobScheduler.Schedule(ref sumJob, handle2); + var handle1 = s_jobScheduler.ScheduleParallel(ref addJob, arraySize, 64); + var handle2 = s_jobScheduler.ScheduleParallel(ref multiplyJob, arraySize, 64); + var handle3 = s_jobScheduler.Schedule(ref sumJob, handle2); - _jobScheduler.WaitComplete(handle3); + s_jobScheduler.Wait(handle3); var expected = ComputeExpectedSum(arraySize); Assert.AreEqual(expected, *result, 0.01f); @@ -226,13 +228,13 @@ public unsafe class TestJobSystem result = result2 }; - var handle1 = _jobScheduler.Schedule(ref job1); - var handle2 = _jobScheduler.Schedule(ref job2); + var handle1 = s_jobScheduler.Schedule(ref job1); + 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, _jobScheduler.GetJobStatus(handle2)); + Assert.AreEqual(JobState.Completed, s_jobScheduler.GetJobStatus(handle1)); + Assert.AreEqual(JobState.Completed, s_jobScheduler.GetJobStatus(handle2)); } [TestMethod] @@ -253,12 +255,12 @@ public unsafe class TestJobSystem result = result2 }; - var handle1 = _jobScheduler.Schedule(ref job1); - var handle2 = _jobScheduler.Schedule(ref job2); + var handle1 = s_jobScheduler.Schedule(ref job1); + 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] @@ -274,7 +276,7 @@ public unsafe class TestJobSystem // 1. Create a "Gatekeeper" vectorJob that spins/blocks a worker thread until signaled. // This allows us to control exactly when the dependency completes. 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. using var barrier = new Barrier(2); @@ -288,7 +290,7 @@ public unsafe class TestJobSystem // CONTENTION POINT: // Trying to add a dependency to 'rootHandle'. // 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); @@ -321,7 +323,7 @@ public unsafe class TestJobSystem } // 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)."); @@ -335,27 +337,71 @@ public unsafe class TestJobSystem var vectorBuf = stackalloc float[size * size]; var vs = new Span(vectorBuf, size * size); - var vectorJob = new Misaki.HighPerformance.Test.Jobs.NoiseJobVector + var vectorJob = new NoiseJobVector { buffers = vectorBuf, width = 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 ss = new Span(spmdBuf, size * size); - var spmdJob = new Misaki.HighPerformance.Test.Jobs.NoiseJobMath + var spmdJob = new NoiseJobMath { buffers = spmdBuf, width = size, height = size, }; - //spmdJob.Run(size * size, -1); + spmdJob.Run(size * size, default); var eq = vs.SequenceCompareTo(ss); Assert.AreEqual(0, eq); } + + [TestMethod] + public void DynamicDispatch() + { + using var arr = new UnsafeArray>(256, Allocator.Persistent); + for (var i = 0; i < arr.Length; i++) + { + arr[i] = new UnsafeArray(256, Allocator.Persistent); + for (var j = 0; j < arr[i].Length; j++) + { + arr[i][j] = j; + } + } + + using var handles = new UnsafeList(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(); + } + } } \ No newline at end of file diff --git a/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobs.cs b/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobs.cs index 90cf5b6..2a3bc12 100644 --- a/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobs.cs +++ b/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobs.cs @@ -9,7 +9,7 @@ internal unsafe struct TwoSumJob : IJob public float* result; - public void Execute(int threadIndex) + public void Execute(ref readonly JobExecutionContext ctx) { *result = value1 + value2; } @@ -21,7 +21,7 @@ internal unsafe struct AddJob : IJob public float* result; - public void Execute(int threadIndex) + public void Execute(ref readonly JobExecutionContext ctx) { *result += value; } @@ -33,7 +33,7 @@ internal unsafe struct KahanSumJob : IJob public int length; public float* output; - public void Execute(int threadIndex) + public void Execute(ref readonly JobExecutionContext ctx) { var sum = 0f; var c = 0f; // Compensation for lost low-order bits @@ -55,7 +55,7 @@ internal unsafe struct ParallelAddJob : IJobParallel public float value; 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++) { @@ -69,7 +69,7 @@ internal unsafe struct ParallelMultiplyJob : IJobParallel public float multiplier; 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++) { @@ -82,7 +82,7 @@ public unsafe struct WaitJob : IJob { public bool* pSignal; - public void Execute(int loopIndex) + public void Execute(ref readonly JobExecutionContext ctx) { var spin = new SpinWait(); while (!Volatile.Read(ref *pSignal)) @@ -96,7 +96,7 @@ public unsafe struct IncrementJob : IJob { public int* pCounter; - public void Execute(int loopIndex) + public void Execute(ref readonly JobExecutionContext ctx) { Interlocked.Increment(ref *pCounter); }