From ea0e6f3ff66ac2b8f052d20def70a56ff2ccffd7 Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:33:58 +0800 Subject: [PATCH 1/3] optimize para performance of buffered stack --- .../ConcurrentCapacityFixedStack.cs | 287 ++++++++++++++++++ StgSharp.Common/Controlling/KeyEnum.cs | 32 +- .../Memory/ConcurrentBufferStack.cs | 124 ++++---- StgSharp.Common/Internal/Graphic/gl.cs | 2 +- .../Threading/BufferExpansionLock.cs | 18 +- .../Numeric/MatrixCompute.Basic.cs | 3 + .../Numeric/MatrixParallel.Publish.cs | 13 +- .../Numeric/MatrixParallelPool.cs | 3 +- .../Numeric/MatrixParallelThread.cs | 5 + StgSharpDebug/Program.cs | 120 +++++++- 10 files changed, 516 insertions(+), 91 deletions(-) create mode 100644 StgSharp.Common/Collections/ConcurrentCapacityFixedStack.cs diff --git a/StgSharp.Common/Collections/ConcurrentCapacityFixedStack.cs b/StgSharp.Common/Collections/ConcurrentCapacityFixedStack.cs new file mode 100644 index 00000000..69dadc9e --- /dev/null +++ b/StgSharp.Common/Collections/ConcurrentCapacityFixedStack.cs @@ -0,0 +1,287 @@ +//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +// file="ConcurrentCapacityFixedStack" +// Project: StgSharp +// AuthorGroup: Nitload +// Copyright (c) Nitload. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace StgSharp.Collections +{ + /// + /// Lock-free, fixed-capacity stack implemented on top of an array using atomic index + /// operations. - No linked list; only array slots - Uses CAS on a shared top index to + /// reserve/release slots - Per-slot state flag to safely publish/consume values under races + /// + public class ConcurrentCapacityFixedStack : IEnumerable + { + + private readonly int[] _states; // 0 = empty, 1 = full + private readonly T[] _values; + private readonly int _capacity; + + // Number of full slots (observable count) + private int _count; + + // Next push position (0.._capacity). Only modified via CAS. + private int _top; + + public ConcurrentCapacityFixedStack(int capacity) + { + if (capacity <= 0) { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + _capacity = capacity; + _values = new T[capacity]; + _states = new int[capacity]; + _top = 0; + _count = 0; + } + + public bool IsEmpty => Count == 0; + + public int Capacity => _capacity; + + public int Count => Volatile.Read(ref _count); + + public void Clear() + { + // Pop all elements to return slots to empty state + while (TryPop(out _)) { } + } + + public IEnumerator GetEnumerator() + { + T[] snap = ToArraySnapshot(); + for (int i = 0; i < snap.Length; i++) { + yield return snap[i]; + } + } + + public T Peek() + { + if (TryPeek(out T? v)) { + return v; + } + + throw new InvalidOperationException("Stack is empty."); + } + + public T Pop() + { + if (TryPop(out T value)) { + return value; + } + + throw new InvalidOperationException("Stack is empty."); + } + + public T[] PopRange(int count) + { + if (count <= 0) { + return Array.Empty(); + } + + T[] result = new T[count]; + int i = 0; + for (; i < count; i++) + { + if (!TryPop(out result[i])) + { + break; + } + } + if (i == count) { + return result; + } + + if (i == 0) { + return Array.Empty(); + } + + T[] trimmed = new T[i]; + Array.Copy(result, trimmed, i); + return trimmed; + } + + public void Push(T item) + { + SpinWait sw = default; + while (true) + { + int oldTop = Volatile.Read(ref _top); + if (oldTop >= _capacity) { + throw new InvalidOperationException("Stack is full."); + } + + if (Interlocked.CompareExchange(ref _top, oldTop + 1, oldTop) != oldTop) + { + sw.SpinOnce(); + continue; // failed to reserve slot + } + + // We exclusively own slot oldTop. Publish value then state. + _values[oldTop] = item; + Volatile.Write(ref _states[oldTop], 1); // publish + _ = Interlocked.Increment(ref _count); + return; + } + } + + public void PushRange(ReadOnlySpan span) + { + for (int i = 0; i < span.Length; i++) { + Push(span[i]); + } + } + + /// + /// Best-effort snapshot for enumeration; not strongly consistent under concurrent + /// mutations. + /// + public T[] ToArraySnapshot() + { + int expected = Math.Min(Volatile.Read(ref _count), _capacity); + if (expected == 0) { + return Array.Empty(); + } + + T[] snap = new T[expected]; + int copied = 0; + int idx = Volatile.Read(ref _top) - 1; + while (idx >= 0 && copied < expected) + { + if (Volatile.Read(ref _states[idx]) == 1) { + snap[copied++] = _values[idx]; + } + idx--; + } + + if (copied == snap.Length) { + return snap; + } + + T[] trimmed = new T[copied]; + Array.Copy(snap, trimmed, copied); + return trimmed; + } + + public bool TryPeek(out T value) + { + value = default!; + SpinWait sw = default; + + while (true) + { + int top = Volatile.Read(ref _top); + if (top <= 0) { + return false; + } + int idx = top - 1; + + // If not yet published or racing, retry/spin + if (Volatile.Read(ref _states[idx]) == 0) + { + if (Volatile.Read(ref _top) != top) + { + continue; // head moved, retry + } + sw.SpinOnce(); + continue; + } + + T v = _values[idx]; + + // Validate consistency + if (Volatile.Read(ref _top) == top && Volatile.Read(ref _states[idx]) == 1) + { + value = v; + return true; + } + } + } + + public bool TryPop(out T value) + { + value = default!; + SpinWait sw = default; + + while (true) + { + int oldTop = Volatile.Read(ref _top); + if (oldTop <= 0) + { + return false; // empty + } + + int newTop = oldTop - 1; // index to consume + if (Interlocked.CompareExchange(ref _top, newTop, oldTop) != oldTop) + { + sw.SpinOnce(); + continue; // failed to reserve element + } + + // If producer hasn't published yet, roll back our reservation and retry. + if (Volatile.Read(ref _states[newTop]) == 0) + { + // attempt to restore top; ok if it fails due to concurrent operations + Interlocked.CompareExchange(ref _top, newTop + 1, newTop); + sw.SpinOnce(); + continue; + } + + value = _values[newTop]; + _values[newTop] = default!; // avoid ref retention + Volatile.Write(ref _states[newTop], 0); // mark empty + Interlocked.Decrement(ref _count); + return true; + } + } + + public int TryPopRange(Span destination) + { + if (destination.IsEmpty) { + return 0; + } + + int i = 0; + for (; i < destination.Length; i++) + { + if (!TryPop(out destination[i])) + { + break; + } + } + return i; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + } +} diff --git a/StgSharp.Common/Controlling/KeyEnum.cs b/StgSharp.Common/Controlling/KeyEnum.cs index 5505e64a..3e94f4ed 100644 --- a/StgSharp.Common/Controlling/KeyEnum.cs +++ b/StgSharp.Common/Controlling/KeyEnum.cs @@ -171,22 +171,22 @@ namespace StgSharp #pragma warning restore CA1027 { - Joystick_1 = glConst.GLFW_JOYSTICK_1, - Joystick_2 = glConst.GLFW_JOYSTICK_2, - Joystick_3 = glConst.GLFW_JOYSTICK_3, - Joystick_4 = glConst.GLFW_JOYSTICK_4, - Joystick_5 = glConst.GLFW_JOYSTICK_5, - Joystick_6 = glConst.GLFW_JOYSTICK_6, - Joystick_7 = glConst.GLFW_JOYSTICK_7, - Joystick_8 = glConst.GLFW_JOYSTICK_8, - Joystick_9 = glConst.GLFW_JOYSTICK_9, - Joystick_10 = glConst.GLFW_JOYSTICK_10, - Joystick_11 = glConst.GLFW_JOYSTICK_11, - Joystick_12 = glConst.GLFW_JOYSTICK_12, - Joystick_13 = glConst.GLFW_JOYSTICK_13, - Joystick_14 = glConst.GLFW_JOYSTICK_14, - Joystick_15 = glConst.GLFW_JOYSTICK_15, - Joystick_16 = glConst.GLFW_JOYSTICK_16, + Joystick_1 = Constant.GLFW_JOYSTICK_1, + Joystick_2 = Constant.GLFW_JOYSTICK_2, + Joystick_3 = Constant.GLFW_JOYSTICK_3, + Joystick_4 = Constant.GLFW_JOYSTICK_4, + Joystick_5 = Constant.GLFW_JOYSTICK_5, + Joystick_6 = Constant.GLFW_JOYSTICK_6, + Joystick_7 = Constant.GLFW_JOYSTICK_7, + Joystick_8 = Constant.GLFW_JOYSTICK_8, + Joystick_9 = Constant.GLFW_JOYSTICK_9, + Joystick_10 = Constant.GLFW_JOYSTICK_10, + Joystick_11 = Constant.GLFW_JOYSTICK_11, + Joystick_12 = Constant.GLFW_JOYSTICK_12, + Joystick_13 = Constant.GLFW_JOYSTICK_13, + Joystick_14 = Constant.GLFW_JOYSTICK_14, + Joystick_15 = Constant.GLFW_JOYSTICK_15, + Joystick_16 = Constant.GLFW_JOYSTICK_16, First = Joystick_1, Last = Joystick_16 diff --git a/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs b/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs index 99171b6b..e045af13 100644 --- a/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs +++ b/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs @@ -65,7 +65,7 @@ namespace StgSharp.HighPerformance.Memory return new ConcurrentBufferStack(4); // Default small capacity } - ConcurrentBufferStack stack = new ConcurrentBufferStack(span.Length); + ConcurrentBufferStack stack = new(span.Length); // Use internal method for efficient bulk initialization @@ -106,12 +106,20 @@ namespace StgSharp.HighPerformance.Memory public void Clear() { - Header* header = (Header*)_buffer; _lock.EnterExpansionProcess(); try { - header->Top = 0; - Volatile.Write(ref _count, 0); + _lock.EnterBufferCopy(); // exclusive while mutating Top + try + { + Header* header = (Header*)_buffer; + header->Top = 0; + Volatile.Write(ref _count, 0); + } + finally + { + _lock.ExitBufferCopy(); + } } finally { @@ -127,29 +135,29 @@ namespace StgSharp.HighPerformance.Memory public T Pop() { - Header* header = (Header*)_buffer; while (true) { - nuint oldTop = Volatile.Read(ref header->Top); - if (oldTop <= 0) { - throw new InvalidOperationException("Stack underflow"); - } - - if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop) + _lock.EnterBufferRead(); // prevent buffer being reallocated while reading/modifying Top + try { - _lock.EnterBufferRead(); - try + Header* header = (Header*)_buffer; + nuint oldTop = Volatile.Read(ref header->Top); + if (oldTop <= 0) { + throw new InvalidOperationException("Stack underflow"); + } + + if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop) { T* dataStart = (T*)(_buffer + sizeof(Header)); _ = Interlocked.Decrement(ref _count); return dataStart[oldTop - 1]; } - finally - { - _lock.ExitBufferRead(); - } } - Thread.Sleep(0); // Yield to avoid busy waiting + finally + { + _lock.ExitBufferRead(); + } + Thread.Sleep(0); } } @@ -157,14 +165,16 @@ namespace StgSharp.HighPerformance.Memory { while (true) { + // Ensure capacity, expanding if needed _lock.EnterMetaDataRead(); try { Header* header = (Header*)_buffer; - nuint oldTop = Volatile.Read(ref header->Top); + nuint topSnapshot = Volatile.Read(ref header->Top); nuint cap = Volatile.Read(ref header->Capacity); - if (oldTop >= cap) + if (topSnapshot >= cap) { + // Need expansion _lock.ExitMetaDataRead(); _lock.EnterExpansionProcess(); try @@ -193,22 +203,6 @@ namespace StgSharp.HighPerformance.Memory } continue; } - - if (Interlocked.CompareExchange(ref header->Top, oldTop + 1, oldTop) == oldTop) - { - _lock.EnterBufferRead(); - try - { - T* dataStart = (T*)(_buffer + sizeof(Header)); - dataStart[oldTop] = item; - _ = Interlocked.Increment(ref _count); - return; - } - finally - { - _lock.ExitBufferRead(); - } - } } finally { @@ -216,39 +210,63 @@ namespace StgSharp.HighPerformance.Memory _lock.ExitMetaDataRead(); } } - Thread.Sleep(0); // Yield to avoid busy waiting + + // Reserve and write under exclusive buffer lock to avoid consumers reading unwritten data + _lock.EnterBufferCopy(); + try + { + Header* header = (Header*)_buffer; + nuint idx = header->Top; // reserve next slot + if (idx >= header->Capacity) + { + // lost race against expander or other producer; retry + continue; + } + + T* dataStart = (T*)(_buffer + sizeof(Header)); + dataStart[idx] = item; // write first + header->Top = idx + 1; // then publish by increasing top + _ = Interlocked.Increment(ref _count); + return; + } + finally + { + _lock.ExitBufferCopy(); + } } } public bool TryPop(out T value) { - Header* header = (Header*)_buffer; while (true) { - nuint oldTop = Volatile.Read(ref header->Top); - if (oldTop <= 0) + _lock.EnterBufferRead(); + try { - value = default; - return false; - } + Header* header = (Header*)_buffer; + nuint oldTop = Volatile.Read(ref header->Top); + if (oldTop <= 0) + { + value = default; + return false; + } - if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop) - { - _lock.EnterBufferRead(); - try + if (Interlocked.CompareExchange(ref header->Top, oldTop - 1, oldTop) == oldTop) { T* dataStart = (T*)(_buffer + sizeof(Header)); value = dataStart[oldTop - 1]; _ = Interlocked.Decrement(ref _count); + return true; } - finally - { - _lock.ExitBufferRead(); - } - return true; } - Thread.Sleep(0); // Yield to avoid busy waiting + finally + { + _lock.ExitBufferRead(); + } + value = default; + return true; } + Thread.Sleep(0); // Yield to avoid busy waiting } // Internal method for efficient bulk initialization diff --git a/StgSharp.Common/Internal/Graphic/gl.cs b/StgSharp.Common/Internal/Graphic/gl.cs index 07ac1aaa..02a7dc6d 100644 --- a/StgSharp.Common/Internal/Graphic/gl.cs +++ b/StgSharp.Common/Internal/Graphic/gl.cs @@ -38,7 +38,7 @@ namespace StgSharp.Internal internal static partial class GraphicFramework { - internal static uint[] GLtype = []; + internal static uint[] GLtype = new uint[16]; static GraphicFramework() { diff --git a/StgSharp.Common/Threading/BufferExpansionLock.cs b/StgSharp.Common/Threading/BufferExpansionLock.cs index 6ecbc3f9..df5fbedb 100644 --- a/StgSharp.Common/Threading/BufferExpansionLock.cs +++ b/StgSharp.Common/Threading/BufferExpansionLock.cs @@ -42,7 +42,7 @@ namespace StgSharp.Threading public class BufferExpansionLock : IDisposable { - private static readonly ThreadLocal Count = new(); + private static readonly ThreadLocal Count = new(() => 0); private readonly ReaderWriterLockSlim _copyLock; private readonly ReaderWriterLockSlim _expansionLock; @@ -74,13 +74,11 @@ namespace StgSharp.Threading [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnterBufferRead() { - if (++Count.Value == 1) - { + int c = Count.Value; + if (c == 0) { _copyLock.EnterReadLock(); - } else - { - Count.Value++; } + Count.Value = c + 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -104,12 +102,10 @@ namespace StgSharp.Threading [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ExitBufferRead() { - if (--Count.Value == 0) - { + int c = Count.Value - 1; + Count.Value = c; + if (c == 0) { _copyLock.ExitReadLock(); - } else - { - Count.Value--; } } diff --git a/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs b/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs index 2af8c137..cfdc1a55 100644 --- a/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs +++ b/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs @@ -91,6 +91,7 @@ namespace StgSharp.Mathematics.Numeric * wait */ } + MatrixParallel.ReleaseParallelLeadership(ref pool); MatrixKernel* leftPtr = left.Buffer; MatrixKernel* rightPtr = right.Buffer; MatrixKernel* ansPtr = ans.Buffer; @@ -98,6 +99,8 @@ namespace StgSharp.Mathematics.Numeric NativeIntrinsic.Intrinsic.f32_sub(leftPtr + i, rightPtr + i, ansPtr + i); } + // thread. + return ans; } Stack taskGroup = MatrixParallel.PublicTask(left.GetColumnEnumeration(), right.GetColumnEnumeration(), ans.GetColumnEnumeration(), diff --git a/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs b/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs index 894aa176..1e138806 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs @@ -820,9 +820,9 @@ namespace StgSharp.Mathematics.Numeric int tileWidth = (1024 / secCount) + 1; int tileCount = (primCount + tileWidth - 1) / tileWidth; - MatrixParallelPool pool = LeadParallel(); + CapacityFixedStack pool = LeadParallel(); - int threadCount = pool.SpareThreadCount; + int threadCount = pool.Count; int tilePerThreadLess = tileCount / threadCount; // tiles per thread without extra slice int columnAfterBaseTile = tileCount % threadCount * tileWidth; // tile sliced into columns int extraColumnPerThread = columnAfterBaseTile / threadCount; @@ -833,7 +833,7 @@ namespace StgSharp.Mathematics.Numeric MatrixParallelTaskPackage* current = package; while (true) { - if (!pool.TryRequestWrap(out MatrixParallelWrap? wrap) || wrap is null) + if (!pool.TryPop(out MatrixParallelThread? wrap) || wrap is null) { #if DEBUG throw new InvalidOperationException("Someone steal parallel wraps from the pool."); @@ -846,7 +846,7 @@ namespace StgSharp.Mathematics.Numeric bool isLargeSlice = extraColumnCount > 0; // count tiles for wrap - int wrapWidth = wrap!.WrapTaskCapacity; + int wrapWidth =/**/ 3/* wrap!.WrapTaskCapacity/**/; int columnFromSlicingCount = (hasSlice ? extraColumnPerThread : 0) * wrapWidth; int baseColumn = wrapWidth * tilePerThreadLess * tileWidth; int columnInWrap = baseColumn + columnFromSlicingCount + (isLargeSlice ? 1 : 0); @@ -872,8 +872,9 @@ namespace StgSharp.Mathematics.Numeric total -= columnInWrap; // set to wrap and push - wrap.WrapTask = (MatrixParallelTaskPackageNonGeneric*)current; - taskBunch.Push(wrap); + wrap.Tasks.Enqueue((IntPtr)current); + + // taskBunch.Push(wrap); if (total <= 0) diff --git a/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs b/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs index bcaa6b75..0370dfaa 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs @@ -65,6 +65,7 @@ namespace StgSharp.Mathematics.Numeric internal CapacityFixedStack SpareWrap { get; set; } + /* public void ReturnIndividualHandle(ref MatrixParallelWrap.Handle handle) { MatrixParallelWrap wrap = handle.Wrap; @@ -108,6 +109,6 @@ namespace StgSharp.Mathematics.Numeric wrap = null!; return false; } - + /**/ } } diff --git a/StgSharp.Mathematics/Numeric/MatrixParallelThread.cs b/StgSharp.Mathematics/Numeric/MatrixParallelThread.cs index c4fa8070..62e6b10e 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallelThread.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallelThread.cs @@ -96,6 +96,11 @@ namespace StgSharp.Mathematics.Numeric _resetEvent.Set(); } + public void ReturnToPool() + { + // MatrixParallel.(this); + } + protected virtual void Dispose(bool disposing) { if (!disposedValue) diff --git a/StgSharpDebug/Program.cs b/StgSharpDebug/Program.cs index f4835a5b..b583fc05 100644 --- a/StgSharpDebug/Program.cs +++ b/StgSharpDebug/Program.cs @@ -2,8 +2,8 @@ // ----------------------------------------------------------------------- // file="Program" // Project: StgSharp -// AuthorGroup: Nitload Space -// Copyright (c) Nitload Space. All rights reserved. +// AuthorGroup: Nitload +// Copyright (c) Nitload. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -40,10 +40,12 @@ using StgSharp.Script; using StgSharp.Script.Express; using System; +using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; +using System.Threading; using System.Xml.XPath; namespace StgSharp @@ -51,7 +53,7 @@ namespace StgSharp internal static partial class Program { - private static string testExpPath = @"D:\Programmer\stepcode\data\ap203\ap203.exp"; + private static string testExpPath = @"D:\\Programmer\\stepcode\\data\\ap203\\ap203.exp"; internal static unsafe void Main(string[] args) { @@ -59,6 +61,118 @@ namespace StgSharp { World.Init(); + // New test with synchronized start: + // - 8 producer threads, each pushing 1000 unique integers + // - 8 consumer threads, each attempting to pop 1000 times + // - Consumers print one line per successful pop + // - Finally, print popped/remaining counts and values + + const int producerThreads = 8; + const int consumerThreads = 8; + const int perProducer = 1000; + const int perConsumerAttempts = 1000; + + ConcurrentBufferStack stack = new ConcurrentBufferStack(); + Thread[] producers = new Thread[producerThreads]; + Thread[] consumers = new Thread[consumerThreads]; + ConcurrentBag popped = new ConcurrentBag(); + + int totalThreads = producerThreads + consumerThreads; + using CountdownEvent ready = new CountdownEvent(totalThreads); + using ManualResetEventSlim startGate = new ManualResetEventSlim(false); + + for (int c = 0; c < consumerThreads; c++) + { + consumers[c] = new Thread(() => { + ready.Signal(); + startGate.Wait(); + + for (int k = 0; k < perConsumerAttempts; k++) + { + if (stack.TryPop(out int v)) + { + popped.Add(v); + Console.WriteLine($"pop: {v}"); + } else + { + Thread.Yield(); + } + } + }) + { + IsBackground = true + }; + } + + for (int p = 0; p < producerThreads; p++) + { + int captured = p; + producers[p] = new Thread(() => { + ready.Signal(); + startGate.Wait(); + + int begin = captured * perProducer; + for (int j = 0; j < perProducer; j++) + { + stack.Push(begin + j); + if ((j & 0xFF) == 0) { + Thread.Yield(); + } + } + }) + { + IsBackground = true + }; + } + + foreach (Thread t in consumers) { + t.Start(); + } + + foreach (Thread t in producers) { + t.Start(); + } + + // Ensure all threads are ready, then start simultaneously + ready.Wait(); + startGate.Set(); + + foreach (Thread t in producers) { + t.Join(); + } + + foreach (Thread t in consumers) { + t.Join(); + } + + // Gather remaining data + List remaining = new System.Collections.Generic.List(); + while (stack.TryPop(out int value)) { + remaining.Add(value); + } + + Console.WriteLine($"Popped count: {popped.Count}"); + Console.WriteLine("Popped values:"); + foreach (int v in popped) + { + Console.Write(v); + Console.Write('\t'); + } + Console.WriteLine(); + + Console.WriteLine($"Remaining count: {remaining.Count}"); + Console.WriteLine("Remaining values:"); + foreach (int v in remaining) + { + Console.Write(v); + Console.Write('\t'); + } + Console.WriteLine(); + + // Sanity check + Console.WriteLine($"Produced: {producerThreads * perProducer}, Popped+Remaining: {popped.Count + remaining.Count}"); + + /* // TestMatrixParallel(); // 原有的矩阵测试代码 From ec1b8dbd512ba19be4c563058bf71e088fda197b Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:12:04 +0800 Subject: [PATCH 2/3] working on dynamic wrapping --- .../ConcurrentCapacityFixedStack.cs | 8 +- .../Memory/ConcurrentBufferStack.cs | 38 +- .../Threading/SamplingAdaptiveBackoff.cs | 457 ------------------ .../Numeric/MatrixCompute.Basic.cs | 8 +- .../Numeric/MatrixParallel.Publish.cs | 24 +- .../Numeric/MatrixParallel.cs | 57 +-- .../Numeric/MatrixParallelPool.cs | 148 +++--- .../Numeric/MatrixParallelWrap.cs | 7 + StgSharpDebug/Program.cs | 10 +- 9 files changed, 145 insertions(+), 612 deletions(-) delete mode 100644 StgSharp.Common/Threading/SamplingAdaptiveBackoff.cs diff --git a/StgSharp.Common/Collections/ConcurrentCapacityFixedStack.cs b/StgSharp.Common/Collections/ConcurrentCapacityFixedStack.cs index 69dadc9e..92884837 100644 --- a/StgSharp.Common/Collections/ConcurrentCapacityFixedStack.cs +++ b/StgSharp.Common/Collections/ConcurrentCapacityFixedStack.cs @@ -105,7 +105,7 @@ namespace StgSharp.Collections public T[] PopRange(int count) { if (count <= 0) { - return Array.Empty(); + return []; } T[] result = new T[count]; @@ -122,7 +122,7 @@ namespace StgSharp.Collections } if (i == 0) { - return Array.Empty(); + return []; } T[] trimmed = new T[i]; @@ -251,7 +251,7 @@ namespace StgSharp.Collections if (Volatile.Read(ref _states[newTop]) == 0) { // attempt to restore top; ok if it fails due to concurrent operations - Interlocked.CompareExchange(ref _top, newTop + 1, newTop); + _ = Interlocked.CompareExchange(ref _top, newTop + 1, newTop); sw.SpinOnce(); continue; } @@ -259,7 +259,7 @@ namespace StgSharp.Collections value = _values[newTop]; _values[newTop] = default!; // avoid ref retention Volatile.Write(ref _states[newTop], 0); // mark empty - Interlocked.Decrement(ref _count); + _ = Interlocked.Decrement(ref _count); return true; } } diff --git a/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs b/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs index e045af13..1e2a2b51 100644 --- a/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs +++ b/StgSharp.Common/HighPerformance/Memory/ConcurrentBufferStack.cs @@ -47,7 +47,7 @@ namespace StgSharp.HighPerformance.Memory return new BufferStack(4); // Default small capacity } - BufferStack stack = new BufferStack(span.Length); + BufferStack stack = new(span.Length); // Use internal method for efficient bulk initialization @@ -82,7 +82,7 @@ namespace StgSharp.HighPerformance.Memory private byte* _buffer; private readonly BufferExpansionLock _lock = new(); - private ulong _count; + private long _count; public ConcurrentBufferStack() { @@ -101,7 +101,7 @@ namespace StgSharp.HighPerformance.Memory public ulong Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Volatile.Read(ref _count); + get => (ulong)Volatile.Read(ref _count); } public void Clear() @@ -141,7 +141,7 @@ namespace StgSharp.HighPerformance.Memory try { Header* header = (Header*)_buffer; - nuint oldTop = Volatile.Read(ref header->Top); + int oldTop = Volatile.Read(ref header->Top); if (oldTop <= 0) { throw new InvalidOperationException("Stack underflow"); } @@ -170,8 +170,8 @@ namespace StgSharp.HighPerformance.Memory try { Header* header = (Header*)_buffer; - nuint topSnapshot = Volatile.Read(ref header->Top); - nuint cap = Volatile.Read(ref header->Capacity); + int topSnapshot = Volatile.Read(ref header->Top); + int cap = Volatile.Read(ref header->Capacity); if (topSnapshot >= cap) { // Need expansion @@ -182,8 +182,8 @@ namespace StgSharp.HighPerformance.Memory header = (Header*)_buffer; if (header->Top >= header->Capacity) { - nuint newCap = header->Capacity * 2; - nuint newSize = (nuint)sizeof(Header) + (newCap * (nuint)sizeof(T)); + int newCap = header->Capacity * 2; + nuint newSize = (nuint)sizeof(Header) + ((nuint)newCap * (nuint)sizeof(T)); _lock.EnterBufferCopy(); try @@ -216,7 +216,7 @@ namespace StgSharp.HighPerformance.Memory try { Header* header = (Header*)_buffer; - nuint idx = header->Top; // reserve next slot + int idx = header->Top; // reserve next slot if (idx >= header->Capacity) { // lost race against expander or other producer; retry @@ -244,7 +244,7 @@ namespace StgSharp.HighPerformance.Memory try { Header* header = (Header*)_buffer; - nuint oldTop = Volatile.Read(ref header->Top); + int oldTop = Volatile.Read(ref header->Top); if (oldTop <= 0) { value = default; @@ -263,10 +263,8 @@ namespace StgSharp.HighPerformance.Memory { _lock.ExitBufferRead(); } - value = default; - return true; + Thread.Sleep(0); } - Thread.Sleep(0); // Yield to avoid busy waiting } // Internal method for efficient bulk initialization @@ -282,28 +280,28 @@ namespace StgSharp.HighPerformance.Memory fixed (T* sourcePtr = span) { Buffer.MemoryCopy(sourcePtr, dataStart, - header->Capacity * (nuint)sizeof(T), + (nuint)header->Capacity * (nuint)sizeof(T), (nuint)span.Length * (nuint)sizeof(T)); } - header->Top = (nuint)span.Length; - Volatile.Write(ref _count, (ulong)span.Length); + header->Top = span.Length; + Volatile.Write(ref _count, span.Length); } private void AllocateBuffer(int capacity) { - nuint size = (nuint)sizeof(Header) + ((nuint)capacity * (nuint)sizeof(T)); + nuint size = (nuint)sizeof(Header) + (nuint)capacity * (nuint)sizeof(T); _buffer = (byte*)NativeMemory.Alloc(size); Header* header = (Header*)_buffer; header->Top = 0; - header->Capacity = (nuint)capacity; + header->Capacity = capacity; } private struct Header { - public nuint Capacity; // max capacity of the stack (number of elements) - public nuint Top; // next available index in the stack + public int Capacity; // max capacity of the stack (number of elements) + public int Top; // next available index in the stack } diff --git a/StgSharp.Common/Threading/SamplingAdaptiveBackoff.cs b/StgSharp.Common/Threading/SamplingAdaptiveBackoff.cs deleted file mode 100644 index 71e7994c..00000000 --- a/StgSharp.Common/Threading/SamplingAdaptiveBackoff.cs +++ /dev/null @@ -1,457 +0,0 @@ -//----------------------------------------------------------------------- -// ----------------------------------------------------------------------- -// file="SamplingAdaptiveBackoff" -// Project: StgSharp -// AuthorGroup: Nitload -// Copyright (c) Nitload. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// ----------------------------------------------------------------------- -// ----------------------------------------------------------------------- -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Threading; - -/// -/// Optimized sampling-based adaptive backoff strategy that re-samples and evaluates the current -/// contention situation and adjusts the strategy after a certain number of fallbacks -/// -public struct SamplingAdaptiveBackoff -{ - /* - private const int CONFIDENCE_THRESHOLD = 3; // Requires N consecutive sampling confirmations to switch strategies - - // Configuration constants - private const int FALLBACK_SAMPLING_THRESHOLD = 64; // Triggers re-sampling every 64 fallbacks - private const int INITIAL_SPINWAIT_THRESHOLD = 8; // Cheap spinning for the first few SpinWait attempts - private const int MAX_SAMPLING_WINDOW_MS = 1000; // Maximum sampling window time - private const int MIN_SAMPLING_WINDOW_MS = 100; // Minimum sampling window time - private const int PERFORMANCE_HISTORY_MASK = 7; // Use 3-bit mask for 8 entries (power of 2) - private const double SIGNIFICANT_IMPROVEMENT = 0.15; // 15% improvement is considered significant - - private BackoffMethod _candidateMethod; - private BackoffMethod _currentMethod; - private byte _confidenceCounter; - private byte _consecutiveFailures; - - // Packed performance data to reduce memory footprint - private PackedMethodPerformance _packedPerformances; // Bit-packed performance data for all 3 methods - private SamplingWindow _currentSample; - - // Instance state - private SpinWait _spinWait; - private uint _rng; - - /// - /// Gets the current method - /// - public readonly BackoffMethod CurrentMethod => _currentMethod; - - /// - /// Creates a new optimized sampling adaptive backoff strategy instance - /// - public static SamplingAdaptiveBackoff Create() - { - // Choose initial strategy based on system characteristics - BackoffMethod initialMethod = DetermineInitialMethod(); - - SamplingAdaptiveBackoff instance = new() - { - _spinWait = default, - _currentMethod = initialMethod, - _consecutiveFailures = 0, - _confidenceCounter = 0, - _candidateMethod = initialMethod, - _rng = GenerateInitialSeed(), - _packedPerformances = PackedMethodPerformance.Initialize() - }; - - // Start the first sampling window - instance._currentSample.Reset(initialMethod); - - return instance; - } - - /// - /// Gets the current sampling status (for debugging) - /// - public readonly (int attempts, int successes, int fallbacks, double successRate, double fallbackRate) GetCurrentSampleStats() - { - return (_currentSample.TotalAttempts, - _currentSample.SuccessfulAttempts, - _currentSample.FallbackCount, - _currentSample.SuccessRate, - _currentSample.FallbackRate); - } - - /// - /// Gets the historical performance of the specified method (for monitoring) - /// - public readonly (double successRate, double fallbackRate, double overallScore) GetMethodPerformance( - BackoffMethod method) - { - var (successRate, fallbackRate, score) = _packedPerformances.GetPerformance(method); - return (successRate, fallbackRate, score); - } - - /// - /// Called when attempting CAS (must be called regardless of success or failure) - /// - /// - /// Whether this CAS attempt was successful - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void OnCASAttempt(bool success) - { - _currentSample.TotalAttempts++; - - if (success) - { - _currentSample.SuccessfulAttempts++; - _consecutiveFailures = 0; - _spinWait = default; // Reset SpinWait after success - } else - { - _consecutiveFailures++; - } - } - - /// - /// Called when CAS fails and fallback needs to be executed, performs backoff and may trigger - /// re-sampling - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void OnFallbackAndBackoff() - { - _currentSample.FallbackCount++; - - // Check if re-sampling threshold is reached using bitwise operations for faster comparison - if ((_currentSample.FallbackCount & ~(FALLBACK_SAMPLING_THRESHOLD - 1)) != 0 || - _currentSample.ElapsedTicks > MAX_SAMPLING_WINDOW_MS) - { - if (_currentSample.ElapsedTicks >= MIN_SAMPLING_WINDOW_MS) { - EvaluateCurrentSample(); - } - } - - // Perform backoff using jump table approach for better branch prediction - PerformBackoffOptimized(); - } - - /// - /// Determines initial method based on system characteristics - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BackoffMethod DetermineInitialMethod() - { - int logicalCores = Environment.ProcessorCount; - return logicalCores <= 16 ? BackoffMethod.SpinWait : BackoffMethod.Sleep0; - } - - /// - /// Determines the method to test in the next sampling window using optimized random selection - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private BackoffMethod DetermineNextTestMethod() - { - // Use bitwise operations for 80%/20% probability split - return (NextRandom() & 0xF) < 13 ? _candidateMethod : (BackoffMethod)(NextRandom() & 3); - } - - /// - /// Evaluates the current sampling window and may adjust the strategy - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private void EvaluateCurrentSample() - { - // Update performance record for the current method using packed data - _packedPerformances.UpdatePerformance( - _currentSample.TestedMethod, _currentSample.SuccessRate, _currentSample.FallbackRate, - _currentSample.ElapsedTicks / Math.Max(1, _currentSample.TotalAttempts)); - - // Find the currently best performing method - BackoffMethod bestMethod = _packedPerformances.FindBestMethod(); - - // Use branchless logic for confidence tracking - bool methodChanged = bestMethod != _currentMethod; - bool candidateMatches = bestMethod == _candidateMethod; - - _confidenceCounter = methodChanged ? (candidateMatches ? (byte)(_confidenceCounter + 1) : (byte)1) : (byte)0; - - if (methodChanged && candidateMatches && _confidenceCounter >= CONFIDENCE_THRESHOLD) - { - SwitchToMethod(bestMethod); - } else if (methodChanged && !candidateMatches) - { - _candidateMethod = bestMethod; - } else if (!methodChanged) { - _candidateMethod = _currentMethod; - } - - // Start new sampling window - BackoffMethod nextTestMethod = DetermineNextTestMethod(); - _currentSample.Reset(nextTestMethod); - - if (nextTestMethod != _currentMethod) - { - _currentMethod = nextTestMethod; - _spinWait = default; - } - } - - /// - /// Generates initial seed for RNG - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint GenerateInitialSeed() - { - return (uint)(Environment.TickCount64 ^ (Thread.CurrentThread.ManagedThreadId * 0x9E3779B9U)); - } - - /// - /// Fast pseudo-random number generator using XorShift32 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private uint NextRandom() - { - // XorShift32 - faster than LCG with better distribution - _rng ^= _rng << 13; - _rng ^= _rng >> 17; - _rng ^= _rng << 5; - return _rng; - } - - /// - /// Optimized backoff operation using jump table approach - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void PerformBackoffOptimized() - { - // Use computed goto pattern for better branch prediction - switch (_currentMethod) - { - case BackoffMethod.SpinWait: - PerformSpinWaitBackoff(); - break; - case BackoffMethod.Yield: - _ = Thread.Yield(); - break; - case BackoffMethod.Sleep0: - Thread.Sleep(0); - break; - default: - break; - } - } - - /// - /// Specialized SpinWait backoff with optimized logic - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void PerformSpinWaitBackoff() - { - if (_consecutiveFailures > INITIAL_SPINWAIT_THRESHOLD && _spinWait.NextSpinWillYield) - { - // Optimized random yield count using bitwise operations - int yields = (int)(NextRandom() & 7) + 1; - for (int i = 0; i < yields; i++) { - _ = Thread.Yield(); - } - } else - { - _spinWait.SpinOnce(sleep1Threshold:-1); - } - } - - /// - /// Switches to a new method with optimized state reset - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SwitchToMethod(BackoffMethod method) - { - _currentMethod = method; - _candidateMethod = method; - _confidenceCounter = 0; - _spinWait = default; - } - - public enum BackoffMethod : byte - { - - SpinWait = 0, - Yield = 1, - Sleep0 = 2 - - } - - // Optimized sampling window state with better packing - private struct SamplingWindow - { - - public BackoffMethod TestedMethod; // Current method being tested - public int FallbackCount; // Number of fallbacks - public int SuccessfulAttempts; // Number of successful attempts - public int TotalAttempts; // Total number of attempts - public long StartTicks; // Sampling window start time - - public readonly double SuccessRate - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => TotalAttempts == 0 ? 0.0 : (double)SuccessfulAttempts / TotalAttempts; - } - - public readonly double FallbackRate - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => TotalAttempts == 0 ? 0.0 : (double)FallbackCount / TotalAttempts; - } - - public readonly long ElapsedTicks - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => Environment.TickCount64 - StartTicks; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset(BackoffMethod method) - { - StartTicks = Environment.TickCount64; - TotalAttempts = 0; - SuccessfulAttempts = 0; - FallbackCount = 0; - TestedMethod = method; - } - - } - - // Bit-packed performance data structure for memory efficiency - private struct PackedMethodPerformance - { - - // Pack all 3 methods' performance into compact format - private ulong _packed0; // Method 0 (SpinWait) data - private ulong _packed1; // Method 1 (Yield) data - private ulong _packed2; // Method 2 (Sleep0) data - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly BackoffMethod FindBestMethod() - { - (double _, double _, double score0) = UnpackPerformanceData(_packed0); - (double _, double _, double score1) = UnpackPerformanceData(_packed1); - (double _, double _, double score2) = UnpackPerformanceData(_packed2); - - // Use branchless comparison for best performance - BackoffMethod best = score1 > score0 + SIGNIFICANT_IMPROVEMENT ? - BackoffMethod.Yield : - BackoffMethod.SpinWait; - best = score2 > GetScore(best) + SIGNIFICANT_IMPROVEMENT ? BackoffMethod.Sleep0 : best; - - return best; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly (double successRate, double fallbackRate, double overallScore) GetPerformance( - BackoffMethod method) - { - ulong packed = method switch - { - BackoffMethod.SpinWait => _packed0, - BackoffMethod.Yield => _packed1, - BackoffMethod.Sleep0 => _packed2, - _ => _packed0 - }; - - return UnpackPerformanceData(packed); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PackedMethodPerformance Initialize() - { - return new PackedMethodPerformance - { - _packed0 = PackPerformanceData(0.5, 0.5, 0), - _packed1 = PackPerformanceData(0.5, 0.5, 0), - _packed2 = PackPerformanceData(0.5, 0.5, 0) - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdatePerformance(BackoffMethod method, double successRate, double fallbackRate, long avgLatency) - { - ulong packed = PackPerformanceData(successRate, fallbackRate, avgLatency); - - switch (method) - { - case BackoffMethod.SpinWait: - _packed0 = packed; - break; - case BackoffMethod.Yield: - _packed1 = packed; - break; - case BackoffMethod.Sleep0: - _packed2 = packed; - break; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly double GetScore(BackoffMethod method) - { - return method switch - { - BackoffMethod.SpinWait => UnpackPerformanceData(_packed0).overallScore, - BackoffMethod.Yield => UnpackPerformanceData(_packed1).overallScore, - BackoffMethod.Sleep0 => UnpackPerformanceData(_packed2).overallScore, - _ => 0.0 - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong PackPerformanceData(double successRate, double fallbackRate, long avgLatency) - { - // Pack data into 64-bit value: 20 bits success rate, 20 bits fallback rate, 24 bits latency - uint success = (uint)(successRate * 1048575); // 20 bits (2^20 - 1) - uint fallback = (uint)(fallbackRate * 1048575); // 20 bits - uint latency = (uint)Math.Min(avgLatency, 16777215); // 24 bits (2^24 - 1) - - return ((ulong)success << 44) | ((ulong)fallback << 24) | latency; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static (double successRate, double fallbackRate, double overallScore) UnpackPerformanceData( - ulong packed) - { - double successRate = ((packed >> 44) & 0xFFFFF) / 1048575.0; - double fallbackRate = ((packed >> 24) & 0xFFFFF) / 1048575.0; - long avgLatency = (long)(packed & 0xFFFFFF); - - // Calculate overall score with optimized formula - double baseScore = successRate * 0.6 + (1.0 - fallbackRate) * 0.4; - double latencyPenalty = avgLatency > 0 ? Math.Min(0.1, avgLatency / 10000.0) : 0; - double overallScore = baseScore - latencyPenalty; - - return (successRate, fallbackRate, overallScore); - } - - } - /**/ - -} \ No newline at end of file diff --git a/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs b/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs index cfdc1a55..9ec89dec 100644 --- a/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs +++ b/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs @@ -49,8 +49,8 @@ namespace StgSharp.Mathematics.Numeric long count = (long)left.KernelColumnLength * left.KernelRowLength; if (count <= 1024) { - CapacityFixedStack pool = MatrixParallel.LeadParallel(); - if (!pool.TryPop(out MatrixParallelThread thread)) + ReadOnlySpan pool = MatrixParallel.LeadParallel(1); + if (pool.Length == 0) { /* * wait @@ -84,8 +84,8 @@ namespace StgSharp.Mathematics.Numeric if (count <= 1024) { // TODO: this is very awkward, blocking thread renting, need a better way to handle small tasks - CapacityFixedStack pool = MatrixParallel.LeadParallel(); - if (!pool.TryPop(out MatrixParallelThread thread)) + ReadOnlySpan pool = MatrixParallel.LeadParallel(1); + if (pool.Length == 0) { /* * wait diff --git a/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs b/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs index 1e138806..47622543 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs @@ -820,9 +820,15 @@ namespace StgSharp.Mathematics.Numeric int tileWidth = (1024 / secCount) + 1; int tileCount = (primCount + tileWidth - 1) / tileWidth; - CapacityFixedStack pool = LeadParallel(); + ReadOnlySpan pool = LeadParallel(tileCount); - int threadCount = pool.Count; + int threadCount = pool.Length; + if (pool.Length == 0) + { + /* + * wait until has enough threads + */ + } int tilePerThreadLess = tileCount / threadCount; // tiles per thread without extra slice int columnAfterBaseTile = tileCount % threadCount * tileWidth; // tile sliced into columns int extraColumnPerThread = columnAfterBaseTile / threadCount; @@ -831,17 +837,8 @@ namespace StgSharp.Mathematics.Numeric int total = primCount; Stack taskBunch = new Stack(threadCount); MatrixParallelTaskPackage* current = package; - while (true) + foreach (MatrixParallelThread thread in pool) { - if (!pool.TryPop(out MatrixParallelThread? wrap) || wrap is null) - { -#if DEBUG - throw new InvalidOperationException("Someone steal parallel wraps from the pool."); -#else - break; -#endif - } - bool hasSlice = columnAfterBaseTile > 0; bool isLargeSlice = extraColumnCount > 0; @@ -872,7 +869,7 @@ namespace StgSharp.Mathematics.Numeric total -= columnInWrap; // set to wrap and push - wrap.Tasks.Enqueue((IntPtr)current); + // wrap.Tasks.Enqueue((IntPtr)current); // taskBunch.Push(wrap); @@ -884,7 +881,6 @@ namespace StgSharp.Mathematics.Numeric current = MatrixParallelFactory.FromExistPackage(current); current->PrimTileOffset += columnInWrap; } - ReleaseParallelLeadership(ref pool); return taskBunch; } diff --git a/StgSharp.Mathematics/Numeric/MatrixParallel.cs b/StgSharp.Mathematics/Numeric/MatrixParallel.cs index 4e1977a7..0ff8b4c9 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallel.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallel.cs @@ -34,45 +34,12 @@ using System.Threading; namespace StgSharp.Mathematics.Numeric { - /* - * Thread wrapping to accelerate matrix operations by dividing threads into groups (pool). - * For M pool, each containing an average of N threads, we satisfy the condition M * N = T - 4, - * where T is the total thread count of the CPU. Typically, for modern consumer CPUs, T is around 16. - * For simplicity, let M * N = 16. - * - * The total time complexity for publishing tasks is O(M + N): - * - O(M) accounts for distributing tasks across M pool. - * - O(N) accounts for threads within each wrap fetching tasks from the wrap queue. - * - * Using the inequality Sqrt(M * N) <= (M + N) / 2 (derived from the arithmetic-geometric mean inequality), - * the optimal configuration occurs when M = N. For M * N = 16, this gives M = N = 4. - * - * To ensure compatibility across a wide range of CPU architectures, - * we allow each wrap to contain either 3 or 4 threads, providing flexibility while maintaining efficiency. - */ - public static partial class MatrixParallel { - private static readonly (int a, int b)[] LookupTable = [ - (1, 1), // 7 - (0, 2), // 8 (no non-zero solution) - (3, 0), // 9 (no non-zero solution) - (2, 1), // 10 - (1, 2), // 11 - (0, 3), // 12 (no non-zero solution) - (3, 1), // 13 - (2, 2), // 14 - (1, 3), // 15 - (4, 1), // 16 - (3, 2), // 17 - (2, 3), // 18 - (1, 4), // 19 - (4, 2), // 20 - ]; private static bool _meaningful; - private static CapacityFixedStack _threads; + private static ConcurrentCapacityFixedStack _threads; private static volatile int RemainThreadCount = 0; private static readonly object _lock = new(); @@ -91,22 +58,6 @@ namespace StgSharp.Mathematics.Numeric Monitor.Exit(_lock); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static (int count_3, int count_4) FastDecompose(int c) - { - if (c < 7) { - return (0, 0); - } - - int t = c - 7; - int k = t / 14; - int d = t % 14; - - (int a0, int b0) = LookupTable[d]; - - return (a0 + (2 * k), b0 + (2 * k)); - } - public static void Init() { if (IsInitialized) { @@ -144,17 +95,17 @@ namespace StgSharp.Mathematics.Numeric #region wrap field - public static CapacityFixedStack LeadParallel() + public static MatrixParallelThread[] LeadParallel(int recommendCount) { #if NET9_0_OR_GREATER #else Monitor.Enter(_lock); #endif - return _threads; + return _threads.PopRange(recommendCount); } - public static void ReleaseParallelLeadership(ref CapacityFixedStack pool) + public static void ReleaseParallelLeadership(ref MatrixParallelThread[] pool) { #if NET9_0_OR_GREATER #else diff --git a/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs b/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs index 0370dfaa..c5efa236 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs @@ -36,79 +36,115 @@ using System.Threading.Tasks; namespace StgSharp.Mathematics.Numeric { - public class MatrixParallelPool + public struct MatrixParallelHandle : IDisposable { - private MatrixParallelPool() - { - throw new NotSupportedException(); - } - /* - internal MatrixParallelPool(int threadCount, ReadOnlySpan wrapSpan) - { - SpareWrap = CapacityFixedStackBuilder.Create(wrapSpan); - SingleThreadAllocation = new CapacityFixedBag(wrapSpan.Length); - SpareThreadCount = threadCount; - } - /**/ + private static readonly (int a, int b)[] LookupTable = [ + (1, 1), // 7 + (0, 2), // 8 (no non-zero solution) + (3, 0), // 9 (no non-zero solution) + (2, 1), // 10 + (1, 2), // 11 + (0, 3), // 12 (no non-zero solution) + (3, 1), // 13 + (2, 2), // 14 + (1, 3), // 15 + (4, 1), // 16 + (3, 2), // 17 + (2, 3), // 18 + (1, 4), // 19 + (4, 2), // 20 + ]; - internal MatrixParallelPool(int threadCount, CapacityFixedStack wrapSpan) + private MatrixParallelThread[] _threads; + private bool _disposed = false; + private CapacityFixedStack _wraps; + + internal MatrixParallelHandle(MatrixParallelThread[] threadArray) { - SpareWrap = wrapSpan; - SingleThreadAllocation = new CapacityFixedBag(wrapSpan.Count); - SpareThreadCount = threadCount; + _threads = threadArray; + IsMultithread = threadArray.Length > 1; + SliceToWraps(); } - public int SpareThreadCount { get; set; } - - internal CapacityFixedBag SingleThreadAllocation { get; set; } - - internal CapacityFixedStack SpareWrap { get; set; } - - /* - public void ReturnIndividualHandle(ref MatrixParallelWrap.Handle handle) + public ReadOnlySpan Threads { - MatrixParallelWrap wrap = handle.Wrap; - handle.Role = 0; - if (wrap.IsAllAvailable) + get { - SingleThreadAllocation.RemoveAt(wrap.Magic); - SpareWrap.Push(wrap); - SpareThreadCount += wrap.WrapTaskCapacity; - } - } - - public bool TryRentIndividualHandle(out MatrixParallelWrap.Handle handle) - { - foreach (MatrixParallelWrap item in SingleThreadAllocation) - { - if (item.TryGetLeaderHandle(out handle!)) { - return true; + if (_disposed) { + throw new ObjectDisposedException(typeof(MatrixParallelHandle).Name); } + return _threads; } - if (TryRequestWrap(out MatrixParallelWrap wrap)) - { - wrap!.Magic = SingleThreadAllocation.Count; - SingleThreadAllocation.Add(wrap); - if (wrap.TryGetLeaderHandle(out handle!)) { - return true; - } + } + + internal ReadOnlySpan ThreadsUnsafe + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _threads; + } + + private bool IsMultithread { get; init; } + + public void Dispose() + { + if (_disposed) { + return; } - handle = null!; - return false; + MatrixParallel.ReleaseParallelLeadership(ref _threads); + _disposed = true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryRequestWrap([NotNull] out MatrixParallelWrap? wrap) + private static (int count_3, int count_4) FastDecompose(int c) { - if (SpareWrap.TryPop(out wrap)) + /* + * Thread wrapping to accelerate matrix operations by dividing threads into groups (pool). + * For M pool, each containing an average of N threads, we satisfy the condition M * N = T - 4, + * where T is the total thread count of the CPU. Typically, for modern consumer CPUs, T is around 16. + * For simplicity, let M * N = 16. + * + * The total time complexity for publishing tasks is O(M + N): + * - O(M) accounts for distributing tasks across M pool. + * - O(N) accounts for threads within each wrap fetching tasks from the wrap queue. + * + * Using the inequality Sqrt(M * N) <= (M + N) / 2 (derived from the arithmetic-geometric mean inequality), + * the optimal configuration occurs when M = N. For M * N = 16, this gives M = N = 4. + * + * To ensure compatibility across a wide range of CPU architectures, + * we allow each wrap to contain either 3 or 4 threads, providing flexibility while maintaining efficiency. + */ + if (c < 7) { - SpareThreadCount -= wrap.WrapTaskCapacity; - return true; + return (0, 0); } - wrap = null!; - return false; + + int t = c - 7; + int k = t / 14; + int d = t % 14; + + (int a0, int b0) = LookupTable[d]; + + return (a0 + (2 * k), b0 + (2 * k)); } - /**/ + + private void SliceToWraps() + { + if (_disposed) { + throw new ObjectDisposedException(typeof(MatrixParallelHandle).Name); + } + if (_wraps is not null) { + return; + } + int count = _threads.Length; + if (count < 6) + { + MatrixParallelWrap wrap = new MatrixParallelWrap(_threads); + _wraps = [ wrap ]; + } + (int c3,int c4) = FastDecompose(_threads.Length); + _wraps = new(c3 + c4); + } + } } diff --git a/StgSharp.Mathematics/Numeric/MatrixParallelWrap.cs b/StgSharp.Mathematics/Numeric/MatrixParallelWrap.cs index 21e4cbbb..16bb7bfb 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallelWrap.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallelWrap.cs @@ -40,6 +40,13 @@ namespace StgSharp.Mathematics.Numeric { private readonly MatrixParallelThread[] _threads; + private int _begin; + + internal MatrixParallelWrap(MatrixParallelThread[] threads) + { + _threads = threads; + Count = threads.Length; + } public MatrixParallelWrap() { diff --git a/StgSharpDebug/Program.cs b/StgSharpDebug/Program.cs index b583fc05..38db3ab8 100644 --- a/StgSharpDebug/Program.cs +++ b/StgSharpDebug/Program.cs @@ -92,7 +92,8 @@ namespace StgSharp if (stack.TryPop(out int v)) { popped.Add(v); - Console.WriteLine($"pop: {v}"); + + // Console.WriteLine($"pop: {v}"); } else { Thread.Yield(); @@ -111,7 +112,7 @@ namespace StgSharp ready.Signal(); startGate.Wait(); - int begin = captured * perProducer; + int begin = captured * perProducer + 10 * perProducer; for (int j = 0; j < perProducer; j++) { stack.Push(begin + j); @@ -146,12 +147,13 @@ namespace StgSharp } // Gather remaining data - List remaining = new System.Collections.Generic.List(); + List remaining = []; while (stack.TryPop(out int value)) { remaining.Add(value); } Console.WriteLine($"Popped count: {popped.Count}"); + /**/ Console.WriteLine("Popped values:"); foreach (int v in popped) { @@ -159,7 +161,7 @@ namespace StgSharp Console.Write('\t'); } Console.WriteLine(); - + /**/ Console.WriteLine($"Remaining count: {remaining.Count}"); Console.WriteLine("Remaining values:"); foreach (int v in remaining) From f959339e1861e7a7e2713b759a0792ac1237e191 Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:22:50 +0800 Subject: [PATCH 3/3] working on dynamic wrapping, a good awaiting is on way --- .../Right Margin Line/Right Margin Line.xml | 2 +- .../Editor/Formatting/Wrapping.CSharp.xml | 4 +- .../Collections/CapacityFixedStack.cs | 3 + .../Internal/Intrinsic/MatInterface.cs | 35 -- .../Mathematics/Graphic/Matrix44.cs | 110 +++--- StgSharp.Common/Mathematics/Graphic/Vec4.cs | 32 +- StgSharp.Common/Mathematics/Linear/IMatrix.cs | 1 + .../Numeric/MatrixCompute.Basic.cs | 50 +-- .../Numeric/MatrixParallel.Await.cs | 116 ++++++ .../Numeric/MatrixParallel.Publish.cs | 329 +++++++++--------- .../Numeric/MatrixParallel.cs | 35 +- ...arallelTask.cs => MatrixParallelHandle.cs} | 0 .../Numeric/MatrixParallelPool.cs | 32 +- .../Numeric/MatrixParallelThread.cs | 45 ++- .../Numeric/MatrixParallelWrap.cs | 6 +- StgSharpDebug/Program.cs | 2 + 16 files changed, 447 insertions(+), 355 deletions(-) delete mode 100644 StgSharp.Common/Internal/Intrinsic/MatInterface.cs create mode 100644 StgSharp.Mathematics/Numeric/MatrixParallel.Await.cs rename StgSharp.Mathematics/Numeric/{MatrixParallelTask.cs => MatrixParallelHandle.cs} (100%) diff --git a/.cr/personal/Editor/Right Margin Line/Right Margin Line.xml b/.cr/personal/Editor/Right Margin Line/Right Margin Line.xml index b18e300f..4f8bf4f3 100644 --- a/.cr/personal/Editor/Right Margin Line/Right Margin Line.xml +++ b/.cr/personal/Editor/Right Margin Line/Right Margin Line.xml @@ -2,7 +2,7 @@ True - + 2 \ No newline at end of file diff --git a/.cr/team/Editor/Formatting/Wrapping.CSharp.xml b/.cr/team/Editor/Formatting/Wrapping.CSharp.xml index ee502938..8b5b7c97 100644 --- a/.cr/team/Editor/Formatting/Wrapping.CSharp.xml +++ b/.cr/team/Editor/Formatting/Wrapping.CSharp.xml @@ -11,11 +11,13 @@ - + + WrapAfter + 4 diff --git a/StgSharp.Common/Collections/CapacityFixedStack.cs b/StgSharp.Common/Collections/CapacityFixedStack.cs index 002e91db..e8389de7 100644 --- a/StgSharp.Common/Collections/CapacityFixedStack.cs +++ b/StgSharp.Common/Collections/CapacityFixedStack.cs @@ -29,6 +29,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Threading; namespace StgSharp.Collections { @@ -45,6 +46,8 @@ namespace StgSharp.Collections _index = -1; } + public T? First => Volatile.Read(ref _index) > 0 ? _values[0] : default; + public bool IsEmpty => _index == -1; public int Count => _index + 1; diff --git a/StgSharp.Common/Internal/Intrinsic/MatInterface.cs b/StgSharp.Common/Internal/Intrinsic/MatInterface.cs deleted file mode 100644 index d2033627..00000000 --- a/StgSharp.Common/Internal/Intrinsic/MatInterface.cs +++ /dev/null @@ -1,35 +0,0 @@ -//----------------------------------------------------------------------- -// ----------------------------------------------------------------------- -// file="MatInterface" -// Project: StgSharp -// AuthorGroup: Nitload -// Copyright (c) Nitload. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// ----------------------------------------------------------------------- -// ----------------------------------------------------------------------- -using System; -using System.Collections.Generic; -using System.Text; - -namespace StgSharp.Internal.Intrinsic -{ - public interface IMat { } -} diff --git a/StgSharp.Common/Mathematics/Graphic/Matrix44.cs b/StgSharp.Common/Mathematics/Graphic/Matrix44.cs index e747948e..dc37b507 100644 --- a/StgSharp.Common/Mathematics/Graphic/Matrix44.cs +++ b/StgSharp.Common/Mathematics/Graphic/Matrix44.cs @@ -27,14 +27,17 @@ // ----------------------------------------------------------------------- using StgSharp.Mathematics.Numeric; using System; +using System.Collections; +using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace StgSharp.Mathematics.Graphic { - [StructLayout(LayoutKind.Explicit, Size = 2 * 16 * sizeof(float) + sizeof(bool), Pack = 16)] - public unsafe struct Matrix44 : IEquatable, IMatrix + [CollectionBuilder(builderType:typeof(Matrix44), methodName: nameof(Matrix44.FromSpan))] + [StructLayout(LayoutKind.Explicit, Size = (2 * 16 * sizeof(float)) + sizeof(bool), Pack = 16)] + public unsafe struct Matrix44 : IEquatable, IMatrix, IEnumerable { [FieldOffset(2 * 64 * sizeof(float))] internal bool isTransposed; @@ -160,12 +163,6 @@ namespace StgSharp.Mathematics.Graphic return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref mat), 16); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe float Det() - { - return 0; - } - public override bool Equals(object? obj) { return obj is Matrix44 x && Equals(x); @@ -176,11 +173,24 @@ namespace StgSharp.Mathematics.Graphic return mat.Equals(other.mat); } + public static Matrix44 FromSpan(ReadOnlySpan rowSpan) + { + Matrix44 mat = new Matrix44(); + + mat.transpose.colum0 = rowSpan[0].vec; + mat.transpose.colum1 = rowSpan[1].vec; + mat.transpose.colum2 = rowSpan[2].vec; + mat.transpose.colum3 = rowSpan[3].vec; + + NativeIntrinsic.Intrinsic.f32_transpose(&mat.transpose, &mat.mat); + mat.isTransposed = false; + return mat; + } + public override int GetHashCode() { - return HashCode.Combine( - mat.colum0.GetHashCode(), mat.colum1.GetHashCode(), mat.colum2.GetHashCode(), - mat.colum3.GetHashCode()); + return HashCode.Combine(mat.colum0.GetHashCode(), mat.colum1.GetHashCode(), mat.colum2.GetHashCode(), + mat.colum3.GetHashCode()); } public override string ToString() @@ -194,11 +204,8 @@ namespace StgSharp.Mathematics.Graphic { if (!isTransposed) { - fixed (ColumnSet4* source = &mat, target = &transpose) - { + fixed (ColumnSet4* source = &mat, target = &transpose) { NativeIntrinsic.Intrinsic.f32_transpose(source, target); - - // InternalIO.Intrinsic.transpose44(source, target); } isTransposed = true; } @@ -207,13 +214,9 @@ namespace StgSharp.Mathematics.Graphic [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe Matrix44 operator -(Matrix44 left, Matrix44 right) { - return new Matrix44 - { - column0 = right.column0 - left.column0, - column1 = right.column1 - left.column1, - column2 = right.column2 - left.column2, - column3 = right.column3 - left.column3 - }; + Matrix44 ret = new(); + NativeIntrinsic.Intrinsic.f32_sub(&left, &right, &ret); + return ret; } public static bool operator !=(Matrix44 left, Matrix44 right) @@ -224,47 +227,37 @@ namespace StgSharp.Mathematics.Graphic [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe Matrix44 operator *(Matrix44 mat, float value) { - return new Matrix44( - mat.mat.colum0 * value, mat.mat.colum1 * value, mat.mat.colum2 * value, - mat.mat.colum3 * value); + return new Matrix44(mat.mat.colum0 * value, mat.mat.colum1 * value, mat.mat.colum2 * value, + mat.mat.colum3 * value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Matrix44 operator *(Matrix44 left, Matrix44 right) { left.InternalTranspose(); - return new Matrix44( - Vector4.Dot(left.transpose.colum0, right.mat.colum0), - Vector4.Dot(left.transpose.colum0, right.mat.colum1), - Vector4.Dot(left.transpose.colum0, right.mat.colum2), - Vector4.Dot(left.transpose.colum0, right.mat.colum3), - - Vector4.Dot(left.transpose.colum1, right.mat.colum0), - Vector4.Dot(left.transpose.colum1, right.mat.colum1), - Vector4.Dot(left.transpose.colum1, right.mat.colum2), - Vector4.Dot(left.transpose.colum1, right.mat.colum3), - - Vector4.Dot(left.transpose.colum2, right.mat.colum0), - Vector4.Dot(left.transpose.colum2, right.mat.colum1), - Vector4.Dot(left.transpose.colum2, right.mat.colum2), - Vector4.Dot(left.transpose.colum2, right.mat.colum3), - - Vector4.Dot(left.transpose.colum3, right.mat.colum0), - Vector4.Dot(left.transpose.colum3, right.mat.colum1), - Vector4.Dot(left.transpose.colum3, right.mat.colum2), - Vector4.Dot(left.transpose.colum3, right.mat.colum3)); + return new Matrix44(Vector4.Dot(left.transpose.colum0, right.mat.colum0), + Vector4.Dot(left.transpose.colum0, right.mat.colum1), + Vector4.Dot(left.transpose.colum0, right.mat.colum2), + Vector4.Dot(left.transpose.colum0, right.mat.colum3), + Vector4.Dot(left.transpose.colum1, right.mat.colum0), + Vector4.Dot(left.transpose.colum1, right.mat.colum1), + Vector4.Dot(left.transpose.colum1, right.mat.colum2), + Vector4.Dot(left.transpose.colum1, right.mat.colum3), + Vector4.Dot(left.transpose.colum2, right.mat.colum0), + Vector4.Dot(left.transpose.colum2, right.mat.colum1), + Vector4.Dot(left.transpose.colum2, right.mat.colum2), + Vector4.Dot(left.transpose.colum2, right.mat.colum3), + Vector4.Dot(left.transpose.colum3, right.mat.colum0), + Vector4.Dot(left.transpose.colum3, right.mat.colum1), + Vector4.Dot(left.transpose.colum3, right.mat.colum2), + Vector4.Dot(left.transpose.colum3, right.mat.colum3)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe Matrix44 operator +(Matrix44 left, Matrix44 right) { - Matrix44 ret = new() - { - column0 = left.column0 + right.column0, - column1 = left.column1 + right.column1, - column2 = left.column2 + right.column2, - column3 = left.column3 + right.column3 - }; + Matrix44 ret = new(); + NativeIntrinsic.Intrinsic.f32_add(&left, &right, &ret); return ret; } @@ -273,5 +266,18 @@ namespace StgSharp.Mathematics.Graphic return left.Equals(right); } + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + ref Vec4 col0 = ref column0; + for (int i = 0; i < 4; i++) { + yield return Unsafe.Add(ref col0, i); + } + } + } } diff --git a/StgSharp.Common/Mathematics/Graphic/Vec4.cs b/StgSharp.Common/Mathematics/Graphic/Vec4.cs index b4186d61..44edf4ae 100644 --- a/StgSharp.Common/Mathematics/Graphic/Vec4.cs +++ b/StgSharp.Common/Mathematics/Graphic/Vec4.cs @@ -36,8 +36,9 @@ using System.Runtime.InteropServices; namespace StgSharp.Mathematics.Graphic { + [CollectionBuilder(builderType:typeof(Vec4), methodName: nameof(Vec4.FromSpan))] [StructLayout(LayoutKind.Explicit, Size = 16, Pack = 16)] - public struct Vec4 : IEquatable, IUnmanagedVector + public unsafe struct Vec4 : IEquatable, IUnmanagedVector { [FieldOffset(0)] internal unsafe fixed float num[4]; @@ -127,11 +128,33 @@ namespace StgSharp.Mathematics.Graphic return left + right; } + public ReadOnlySpan AsSpan() + { + return MemoryMarshal.CreateReadOnlySpan(ref reg.Member(0), 4); + } + public override bool Equals(object obj) { return obj is Vec4 v && v == this; } + public static Vec4 FromSpan(ReadOnlySpan span) + { + return new Vec4 + { + X = span[0], + Y = span[1], + Z = span[2], + W = span[3] + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan.Enumerator GetEnumerator() + { + return AsSpan().GetEnumerator(); + } + public override int GetHashCode() { return vec.GetHashCode(); @@ -163,11 +186,8 @@ namespace StgSharp.Mathematics.Graphic public static Vec4 operator *(Matrix44 mat, Vec4 vec) { mat.InternalTranspose(); - return new Vec4( - Vector4.Dot(mat.transpose.colum0, vec.vec), - Vector4.Dot(mat.transpose.colum1, vec.vec), - Vector4.Dot(mat.transpose.colum2, vec.vec), - Vector4.Dot(mat.transpose.colum3, vec.vec)); + return new Vec4(Vector4.Dot(mat.transpose.colum0, vec.vec), Vector4.Dot(mat.transpose.colum1, vec.vec), + Vector4.Dot(mat.transpose.colum2, vec.vec), Vector4.Dot(mat.transpose.colum3, vec.vec)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/StgSharp.Common/Mathematics/Linear/IMatrix.cs b/StgSharp.Common/Mathematics/Linear/IMatrix.cs index e19a0f81..f626e927 100644 --- a/StgSharp.Common/Mathematics/Linear/IMatrix.cs +++ b/StgSharp.Common/Mathematics/Linear/IMatrix.cs @@ -30,6 +30,7 @@ using StgSharp.Graphics; using System; using System.Collections.Generic; using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; namespace StgSharp.Mathematics.Numeric diff --git a/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs b/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs index 9ec89dec..5dd527ac 100644 --- a/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs +++ b/StgSharp.Mathematics/Numeric/MatrixCompute.Basic.cs @@ -47,6 +47,16 @@ namespace StgSharp.Mathematics.Numeric } Matrix ans = Matrix.FromDefault(left.ColumnLength, left.RowLength); long count = (long)left.KernelColumnLength * left.KernelRowLength; + if (count <= 256) + { + MatrixKernel* leftPtr = left.Buffer; + MatrixKernel* rightPtr = right.Buffer; + MatrixKernel* ansPtr = ans.Buffer; + for (int i = 0; i < count; i++) { + NativeIntrinsic.Intrinsic.f32_add(leftPtr + i, rightPtr + i, ansPtr + i); + } + return ans; + } if (count <= 1024) { ReadOnlySpan pool = MatrixParallel.LeadParallel(1); @@ -64,50 +74,14 @@ namespace StgSharp.Mathematics.Numeric } return ans; } - Stack taskGroup = MatrixParallel.PublicTask(left.GetColumnEnumeration(), right.GetColumnEnumeration(), ans.GetColumnEnumeration(), + MatrixParallelHandle taskGroup = MatrixParallel.PublicTask(left.GetColumnEnumeration(), right.GetColumnEnumeration(), ans.GetColumnEnumeration(), (delegate**, MatrixKernel*, MatrixKernel*, void>)NativeIntrinsic.Intrinsic.f32_add); MatrixParallel.LaunchParallel(taskGroup, SleepMode.DeepSleep); /* * run parallel */ - return ans; - } - - public static unsafe Matrix Sub(Matrix left, Matrix right) - { - if (left.ColumnLength != right.ColumnLength || left.RowLength != right.RowLength) { - throw new IndexOutOfRangeException("Matrix dimensions must agree."); - } - Matrix ans = Matrix.FromDefault(left.ColumnLength, left.RowLength); - long count = (long)left.KernelColumnLength * left.KernelRowLength; - if (count <= 1024) - { - // TODO: this is very awkward, blocking thread renting, need a better way to handle small tasks - ReadOnlySpan pool = MatrixParallel.LeadParallel(1); - if (pool.Length == 0) - { - /* - * wait - */ - } - MatrixParallel.ReleaseParallelLeadership(ref pool); - MatrixKernel* leftPtr = left.Buffer; - MatrixKernel* rightPtr = right.Buffer; - MatrixKernel* ansPtr = ans.Buffer; - for (int i = 0; i < count; i++) { - NativeIntrinsic.Intrinsic.f32_sub(leftPtr + i, rightPtr + i, ansPtr + i); - } - - // thread. - - return ans; - } - Stack taskGroup = MatrixParallel.PublicTask(left.GetColumnEnumeration(), right.GetColumnEnumeration(), ans.GetColumnEnumeration(), - (delegate**, MatrixKernel*, MatrixKernel*, void>)NativeIntrinsic.Intrinsic.f32_sub); - /* - * run parallel - */ + taskGroup.Dispose(); return ans; } diff --git a/StgSharp.Mathematics/Numeric/MatrixParallel.Await.cs b/StgSharp.Mathematics/Numeric/MatrixParallel.Await.cs new file mode 100644 index 00000000..a64d2b77 --- /dev/null +++ b/StgSharp.Mathematics/Numeric/MatrixParallel.Await.cs @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +// file="MatrixParallel.Await" +// Project: StgSharp +// AuthorGroup: Nitload +// Copyright (c) Nitload. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StgSharp.Mathematics.Numeric +{ + public static partial class MatrixParallel + { + + private static ConcurrentQueue _awaitingLeaderHandle; + + private static volatile int _createGeneration = 0; + private static int _sortState; + + public static MatrixParallelThread[] LeadParallel(int recommendCount) + { + int min = int.Min(recommendCount, _threads.Capacity); + min /= 2; + MatrixParallelThread[] ret; + if (_threads.Count < min) + { + WaitHandle handle = new WaitHandle + { + CreateGeneration = _createGeneration, + LeastCount = min, + BestCount = recommendCount + }; + _awaitingLeaderHandle.Enqueue(handle); + int cycleGeneration = _createGeneration; + while (true) + { + // TODO 排队等待,对前八个按照优先级排序,优先级最高的独占进行等待,其他继续排队 + if (_createGeneration != cycleGeneration) + { + if (Interlocked.Exchange(ref _sortState, 1) == 0) + { + // TODO 排序 + _ = Interlocked.Exchange(ref _sortState, 0); + } else + { + cycleGeneration = _createGeneration; + continue; + } + } + if (_awaitingLeaderHandle.TryPeek(out WaitHandle? p) && p == handle) + { + // get the leader + ret = _threads.PopRange(recommendCount); + _awaitingLeaderHandle.TryDequeue(out _); + break; + } + Thread.Sleep(0); + } + } else + { + // directly get the leader + ret = _threads.PopRange(recommendCount); + } + _ = Interlocked.Increment(ref _createGeneration); + return ret; + } + + public static void ReturnParallelResources(ref MatrixParallelThread[] pool) + { + _threads.PushRange(pool); + pool = null!; + } + + private record class WaitHandle + { + + public int CreateGeneration { get; set; } + + public int LeastCount { get; set; } + + public int BestCount { get; set; } + + public int CurrentPriority(int currentGeneration) + { + return (currentGeneration - CreateGeneration) * (MatrixParallel._threads.Capacity - LeastCount); + } + + } + + } +} diff --git a/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs b/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs index 47622543..eda3cf23 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallel.Publish.cs @@ -40,10 +40,10 @@ namespace StgSharp.Mathematics.Numeric public static partial class MatrixParallel { - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation) where T: unmanaged, INumber { MatrixParallelTaskPackage* package = MatrixParallelFactory.CreateBaseTask(); @@ -66,11 +66,11 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation) where T: unmanaged, INumber { MatrixParallelTaskPackage* package = MatrixParallelFactory.CreateBaseTask(); @@ -99,11 +99,11 @@ namespace StgSharp.Mathematics.Numeric } // Unary with 1..8 scalars (each scalar is independent generic type) - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0) where T: unmanaged, INumber where TScalar0: unmanaged, INumber { @@ -130,12 +130,12 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -165,12 +165,12 @@ namespace StgSharp.Mathematics.Numeric } // Binary with 1..8 scalars (each scalar is independent generic type) - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0) where T: unmanaged, INumber where TScalar0: unmanaged, INumber { @@ -202,13 +202,13 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -239,13 +239,13 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -279,14 +279,14 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -319,14 +319,14 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -362,15 +362,15 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3, - TScalar4 s4) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3, + TScalar4 s4) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -405,15 +405,15 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -451,16 +451,16 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3, - TScalar4 s4, - TScalar5 s5) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3, + TScalar4 s4, + TScalar5 s5) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -497,16 +497,16 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3, - TScalar4 s4) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3, + TScalar4 s4) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -546,17 +546,17 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3, - TScalar4 s4, - TScalar5 s5, - TScalar6 s6) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3, + TScalar4 s4, + TScalar5 s5, + TScalar6 s6) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -595,17 +595,17 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3, - TScalar4 s4, - TScalar5 s5) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3, + TScalar4 s4, + TScalar5 s5) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -647,18 +647,18 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration source, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3, - TScalar4 s4, - TScalar5 s5, - TScalar6 s6, - TScalar7 s7) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration source, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3, + TScalar4 s4, + TScalar5 s5, + TScalar6 s6, + TScalar7 s7) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -699,18 +699,18 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3, - TScalar4 s4, - TScalar5 s5, - TScalar6 s6) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3, + TScalar4 s4, + TScalar5 s5, + TScalar6 s6) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -754,19 +754,19 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - public static unsafe Stack PublicTask( - MatrixSegmentEnumeration left, - MatrixSegmentEnumeration right, - MatrixSegmentEnumeration ans, - delegate**, MatrixKernel*, MatrixKernel*, void> Operation, - TScalar0 s0, - TScalar1 s1, - TScalar2 s2, - TScalar3 s3, - TScalar4 s4, - TScalar5 s5, - TScalar6 s6, - TScalar7 s7) + public static unsafe MatrixParallelHandle PublicTask( + MatrixSegmentEnumeration left, + MatrixSegmentEnumeration right, + MatrixSegmentEnumeration ans, + delegate**, MatrixKernel*, MatrixKernel*, void> Operation, + TScalar0 s0, + TScalar1 s1, + TScalar2 s2, + TScalar3 s3, + TScalar4 s4, + TScalar5 s5, + TScalar6 s6, + TScalar7 s7) where T: unmanaged, INumber where TScalar0: unmanaged, INumber where TScalar1: unmanaged, INumber @@ -812,7 +812,7 @@ namespace StgSharp.Mathematics.Numeric return PublicTaskPrivate(package); } - private static unsafe Stack PublicTaskPrivate(MatrixParallelTaskPackage* package) + private static unsafe MatrixParallelHandle PublicTaskPrivate(MatrixParallelTaskPackage* package) where T: unmanaged, INumber { int primCount = package->PrimCount; @@ -820,15 +820,21 @@ namespace StgSharp.Mathematics.Numeric int tileWidth = (1024 / secCount) + 1; int tileCount = (primCount + tileWidth - 1) / tileWidth; - ReadOnlySpan pool = LeadParallel(tileCount); + MatrixParallelThread[] pool = LeadParallel(tileCount); - int threadCount = pool.Length; if (pool.Length == 0) { /* * wait until has enough threads */ } + MatrixParallelHandle handle = new MatrixParallelHandle(pool); + if (handle.Wraps.Count() == 1) + { + handle.Wraps.First().WrapTask = (MatrixParallelTaskPackageNonGeneric*)package; + return handle; + } + int threadCount = pool.Length; int tilePerThreadLess = tileCount / threadCount; // tiles per thread without extra slice int columnAfterBaseTile = tileCount % threadCount * tileWidth; // tile sliced into columns int extraColumnPerThread = columnAfterBaseTile / threadCount; @@ -837,13 +843,13 @@ namespace StgSharp.Mathematics.Numeric int total = primCount; Stack taskBunch = new Stack(threadCount); MatrixParallelTaskPackage* current = package; - foreach (MatrixParallelThread thread in pool) + foreach (MatrixParallelWrap wrap in handle.Wraps) { bool hasSlice = columnAfterBaseTile > 0; bool isLargeSlice = extraColumnCount > 0; // count tiles for wrap - int wrapWidth =/**/ 3/* wrap!.WrapTaskCapacity/**/; + int wrapWidth = wrap!.WrapTaskCapacity/**/; int columnFromSlicingCount = (hasSlice ? extraColumnPerThread : 0) * wrapWidth; int baseColumn = wrapWidth * tilePerThreadLess * tileWidth; int columnInWrap = baseColumn + columnFromSlicingCount + (isLargeSlice ? 1 : 0); @@ -869,10 +875,7 @@ namespace StgSharp.Mathematics.Numeric total -= columnInWrap; // set to wrap and push - // wrap.Tasks.Enqueue((IntPtr)current); - - // taskBunch.Push(wrap); - + wrap.WrapTask = (MatrixParallelTaskPackageNonGeneric*)current; if (total <= 0) { @@ -882,7 +885,7 @@ namespace StgSharp.Mathematics.Numeric current->PrimTileOffset += columnInWrap; } - return taskBunch; + return handle; } } diff --git a/StgSharp.Mathematics/Numeric/MatrixParallel.cs b/StgSharp.Mathematics/Numeric/MatrixParallel.cs index 0ff8b4c9..91d876ef 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallel.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallel.cs @@ -27,6 +27,7 @@ // ----------------------------------------------------------------------- using StgSharp.Collections; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -74,46 +75,18 @@ namespace StgSharp.Mathematics.Numeric for (int i = 0; i < count; i++) { _threads.Push(new()); } + _awaitingLeaderHandle = new ConcurrentQueue(); IsInitialized = true; } - public static void LaunchParallel(IEnumerable wraps, SleepMode afterWork) + public static void LaunchParallel(MatrixParallelHandle handle, SleepMode afterWork) { RemainThreadCount = _threads.Count; _AfterWork = (ThreadPriority)afterWork; - ResetThreadCount(); - foreach (MatrixParallelWrap w in wraps) { + foreach (MatrixParallelWrap w in handle.Wraps) { w.SchedulerReset.Set(); } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResetThreadCount() - { - RemainThreadCount = _threads.Count; - } - - #region wrap field - - public static MatrixParallelThread[] LeadParallel(int recommendCount) - { -#if NET9_0_OR_GREATER -#else - Monitor.Enter(_lock); -#endif - - return _threads.PopRange(recommendCount); - } - - public static void ReleaseParallelLeadership(ref MatrixParallelThread[] pool) - { -#if NET9_0_OR_GREATER -#else - Monitor.Exit(_lock); -#endif - pool = null!; - } - - #endregion } } \ No newline at end of file diff --git a/StgSharp.Mathematics/Numeric/MatrixParallelTask.cs b/StgSharp.Mathematics/Numeric/MatrixParallelHandle.cs similarity index 100% rename from StgSharp.Mathematics/Numeric/MatrixParallelTask.cs rename to StgSharp.Mathematics/Numeric/MatrixParallelHandle.cs diff --git a/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs b/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs index c5efa236..8553fcfb 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallelPool.cs @@ -78,6 +78,17 @@ namespace StgSharp.Mathematics.Numeric } } + internal IEnumerable Wraps + { + get + { + if (_disposed) { + throw new ObjectDisposedException(typeof(MatrixParallelHandle).Name); + } + return _wraps; + } + } + internal ReadOnlySpan ThreadsUnsafe { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -91,10 +102,16 @@ namespace StgSharp.Mathematics.Numeric if (_disposed) { return; } - MatrixParallel.ReleaseParallelLeadership(ref _threads); + MatrixParallel.ReturnParallelResources(ref _threads); _disposed = true; } + public MatrixParallelThread GetLeader() + { + Threads[0].Role = 2; + return Threads[0]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static (int count_3, int count_4) FastDecompose(int c) { @@ -139,11 +156,22 @@ namespace StgSharp.Mathematics.Numeric int count = _threads.Length; if (count < 6) { - MatrixParallelWrap wrap = new MatrixParallelWrap(_threads); + MatrixParallelWrap wrap = new MatrixParallelWrap(_threads, 0, count); _wraps = [ wrap ]; } (int c3,int c4) = FastDecompose(_threads.Length); _wraps = new(c3 + c4); + int begin = 0; + for (int i = 0; i < c3; i++) + { + _wraps.Push(new MatrixParallelWrap(_threads, begin, 3)); + begin += 3; + } + for (int i = 0; i < c4; i++) + { + _wraps.Push(new MatrixParallelWrap(_threads, begin, 4)); + begin += 4; + } } } diff --git a/StgSharp.Mathematics/Numeric/MatrixParallelThread.cs b/StgSharp.Mathematics/Numeric/MatrixParallelThread.cs index 62e6b10e..e123e41a 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallelThread.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallelThread.cs @@ -118,30 +118,27 @@ namespace StgSharp.Mathematics.Numeric private unsafe void MatrixParallelWorker() { // Thread self = Thread.CurrentThread; - do + _resetEvent.Wait(); + _resetEvent.Reset(); + _managedThread.Priority = ThreadPriority.Highest; + switch (Role) { - _resetEvent.Wait(); - _resetEvent.Reset(); - _managedThread.Priority = ThreadPriority.Highest; - switch (Role) - { - case 0://worker - // _ = Interlocked.Decrement(ref RemainThreadCount); - ExecuteTaskPackage(Tasks); - break; - case 1://scheduler - BindedWrap.PublishTaskToWorkers(Tasks); - BindedWrap.SetAllWorker(); - ExecuteTaskPackage(Tasks); + case 0://worker + // _ = Interlocked.Decrement(ref RemainThreadCount); + ExecuteTaskPackage(Tasks); + break; + case 1://scheduler + BindedWrap.PublishTaskToWorkers(Tasks); + BindedWrap.SetAllWorker(); + ExecuteTaskPackage(Tasks); - break; - case 2://leader, error because leader should be at outer thread but not here - break; - default: - break; - } - _managedThread.Priority = AfterWork; - } while (true); + break; + case 2://leader, error because leader should be at outer thread but not here + break; + default: + break; + } + _managedThread.Priority = AfterWork; } #region wrap belong @@ -150,10 +147,10 @@ namespace StgSharp.Mathematics.Numeric internal Queue Tasks { get; } = []; - internal int Role + public int Role { get; - set + internal set { if ((Role & (~3u)) == 0) { diff --git a/StgSharp.Mathematics/Numeric/MatrixParallelWrap.cs b/StgSharp.Mathematics/Numeric/MatrixParallelWrap.cs index 16bb7bfb..e1d80fbd 100644 --- a/StgSharp.Mathematics/Numeric/MatrixParallelWrap.cs +++ b/StgSharp.Mathematics/Numeric/MatrixParallelWrap.cs @@ -42,10 +42,12 @@ namespace StgSharp.Mathematics.Numeric private readonly MatrixParallelThread[] _threads; private int _begin; - internal MatrixParallelWrap(MatrixParallelThread[] threads) + internal MatrixParallelWrap(MatrixParallelThread[] threads, int begin, int count) { _threads = threads; + _begin = begin; Count = threads.Length; + _threads[_begin].Role = 1; } public MatrixParallelWrap() @@ -90,7 +92,7 @@ namespace StgSharp.Mathematics.Numeric int primAverage = primCount / Count; int secAverage = secCount / Count; bool shouldSliceSec = secAverage > 512; - for (int i = 0; i < Count; i++) + for (int i = _begin; i < _begin + Count; i++) { Queue tasks = _threads[i].Tasks; MatrixParallelTaskPackageNonGeneric* subTask = MatrixParallelFactory.FromExistPackage(taskPackage); diff --git a/StgSharpDebug/Program.cs b/StgSharpDebug/Program.cs index 38db3ab8..cf61ef1f 100644 --- a/StgSharpDebug/Program.cs +++ b/StgSharpDebug/Program.cs @@ -32,6 +32,7 @@ using StgSharp.Graphics; using StgSharp.HighPerformance; using StgSharp.HighPerformance.Memory; using StgSharp.Mathematics; +using StgSharp.Mathematics.Graphic; using StgSharp.Mathematics.Numeric; using StgSharp.Model.Step; using StgSharp.MVVM; @@ -61,6 +62,7 @@ namespace StgSharp { World.Init(); + // New test with synchronized start: // - 8 producer threads, each pushing 1000 unique integers // - 8 consumer threads, each attempting to pop 1000 times