From cd7636ddbe432b3b6d9def9fa537a5ed0455b76b Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 15 Nov 2025 13:45:24 +0100 Subject: [PATCH] Manual accelerator stream creations replaced with pool --- EvenPerfectBitScanner/Program.cs | 1 - .../Gpu/GpuKernelCompilationTests.cs | 14 +- .../Gpu/Accelerators/HeuristicGpuTables.cs | 90 +++++++------ .../HeuristicPrimeTesterAccelerator.cs | 12 +- PerfectNumbers.Core/Gpu/GpuKernelPool.cs | 20 ++- .../Gpu/InputOutputScratchBuffers.cs | 126 ------------------ .../Gpu/MersenneNumberIncrementalGpuTester.cs | 2 +- .../Gpu/MersenneNumberOrderGpuTester.cs | 2 +- .../Gpu/MersenneNumberResidueGpuTester.cs | 3 +- .../Gpu/PrimeOrderGpuHeuristics.cs | 89 ++++++------- PerfectNumbers.Core/MersenneDivisorCycles.cs | 27 ++-- PerfectNumbers.Core/MersenneNumberTester.cs | 7 +- .../PrimeOrderCalculator.Gpu.cs | 4 +- PerfectNumbers.Core/PrimeTester.cs | 3 - 14 files changed, 133 insertions(+), 267 deletions(-) delete mode 100644 PerfectNumbers.Core/Gpu/InputOutputScratchBuffers.cs diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index af4a93b..29d0598 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -66,7 +66,6 @@ internal static class Program Console.WriteLine("Warming up GPU kernels"); PrimeTester.WarmUpGpuKernels(1024); // PrimeTester.WarmUpGpuKernels(gpuPrimeThreads >> 4); - Console.WriteLine("Warming up scratch buffers"); Console.WriteLine("Starting up threads..."); _ = UnboundedTaskScheduler.Instance; int blockSize = Math.Max(1, _cliArguments.BlockSize); diff --git a/PerfectNumbers.Core.Tests/Gpu/GpuKernelCompilationTests.cs b/PerfectNumbers.Core.Tests/Gpu/GpuKernelCompilationTests.cs index d3bdf9c..b40333b 100644 --- a/PerfectNumbers.Core.Tests/Gpu/GpuKernelCompilationTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/GpuKernelCompilationTests.cs @@ -3,6 +3,7 @@ using FluentAssertions; using ILGPU; using ILGPU.Runtime; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests.Gpu; @@ -348,10 +349,11 @@ public class GpuKernelCompilationTests { GpuPrimeWorkLimiter.Acquire(); - AcceleratorStream stream = accelerator.CreateStream(); + int acceleratorIndex = AcceleratorPool.Shared.Rent(); + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); try { - var kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, _kernelType); + var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType); _ = kernels.Order; _ = kernels.Incremental; _ = kernels.Pow2Mod; @@ -361,15 +363,15 @@ public class GpuKernelCompilationTests finally { stream.Synchronize(); - stream.Dispose(); + AcceleratorStreamPool.Return(acceleratorIndex, stream); // GpuPrimeWorkLimiter.Release(); } - stream = accelerator.CreateStream(); + stream = AcceleratorStreamPool.Rent(acceleratorIndex); GpuPrimeWorkLimiter.Acquire(); try { - var kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, _kernelType); + var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType); _ = kernels.Order; _ = kernels.Incremental; _ = kernels.Pow2Mod; @@ -379,7 +381,7 @@ public class GpuKernelCompilationTests finally { stream.Synchronize(); - stream.Dispose(); + AcceleratorStreamPool.Return(acceleratorIndex, stream); // GpuPrimeWorkLimiter.Release(); } } diff --git a/PerfectNumbers.Core/Gpu/Accelerators/HeuristicGpuTables.cs b/PerfectNumbers.Core/Gpu/Accelerators/HeuristicGpuTables.cs index 148a405..36e3ff2 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/HeuristicGpuTables.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/HeuristicGpuTables.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; @@ -6,50 +5,65 @@ namespace PerfectNumbers.Core.Gpu.Accelerators; internal sealed class HeuristicGpuTables { - private static readonly ConcurrentDictionary _sharedTables = new(20_480, 1); + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; - internal static HeuristicGpuTables EnsureStaticTables(Accelerator accelerator, AcceleratorStream stream) - => _sharedTables.GetOrAdd(accelerator, _ => new HeuristicGpuTables(accelerator, stream)); + private static readonly HeuristicGpuTables[] _sharedTables = new HeuristicGpuTables[PerfectNumberConstants.RollingAccelerators]; + + private static readonly SemaphoreSlim[] _locks = [..Enumerable.Range(1, PerfectNumberConstants.RollingAccelerators).Select(_ => new SemaphoreSlim(1))]; + + internal static HeuristicGpuTables EnsureStaticTables(int acceleratorIndex, AcceleratorStream stream) + { + var @lock = _locks[acceleratorIndex]; + @lock.Wait(); + if (_sharedTables[acceleratorIndex] is {} existing) + { + @lock.Release(); + return existing; + } + + var accelerator = _accelerators[acceleratorIndex]; + existing = new(accelerator, stream); + _sharedTables[acceleratorIndex] = existing; + @lock.Release(); + return existing; + } internal HeuristicGpuTables(Accelerator accelerator, AcceleratorStream stream) { - // lock (accelerator) - { - var heuristicGroupA = HeuristicPrimeSieves.GroupADivisorsStorage; - HeuristicGroupADivisors = CopySpanToDevice(accelerator, stream, heuristicGroupA); + var heuristicGroupA = HeuristicPrimeSieves.GroupADivisorsStorage; + HeuristicGroupADivisors = CopySpanToDevice(accelerator, stream, heuristicGroupA); - var heuristicGroupASquares = HeuristicPrimeSieves.GroupADivisorSquaresStorage; - HeuristicGroupADivisorSquares = CopySpanToDevice(accelerator, stream, heuristicGroupASquares); + var heuristicGroupASquares = HeuristicPrimeSieves.GroupADivisorSquaresStorage; + HeuristicGroupADivisorSquares = CopySpanToDevice(accelerator, stream, heuristicGroupASquares); - HeuristicGroupBDivisorsEnding1 = CopyUintSpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesLastOneWithoutLastThree); - HeuristicGroupBDivisorSquaresEnding1 = CopySpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree); + HeuristicGroupBDivisorsEnding1 = CopyUintSpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesLastOneWithoutLastThree); + HeuristicGroupBDivisorSquaresEnding1 = CopySpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree); - HeuristicGroupBDivisorsEnding7 = CopyUintSpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree); - HeuristicGroupBDivisorSquaresEnding7 = CopySpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree); + HeuristicGroupBDivisorsEnding7 = CopyUintSpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree); + HeuristicGroupBDivisorSquaresEnding7 = CopySpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree); - HeuristicGroupBDivisorsEnding9 = CopyUintSpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesLastNineWithoutLastThree); - HeuristicGroupBDivisorSquaresEnding9 = CopySpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree); + HeuristicGroupBDivisorsEnding9 = CopyUintSpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesLastNineWithoutLastThree); + HeuristicGroupBDivisorSquaresEnding9 = CopySpanToDevice(accelerator, stream, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree); - var combinedEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1; - HeuristicCombinedDivisorsEnding1 = CopySpanToDevice(accelerator, stream, combinedEnding1); - var combinedSquaresEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1Squares; - HeuristicCombinedDivisorSquaresEnding1 = CopySpanToDevice(accelerator, stream, combinedSquaresEnding1); + var combinedEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1; + HeuristicCombinedDivisorsEnding1 = CopySpanToDevice(accelerator, stream, combinedEnding1); + var combinedSquaresEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1Squares; + HeuristicCombinedDivisorSquaresEnding1 = CopySpanToDevice(accelerator, stream, combinedSquaresEnding1); - var combinedEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3; - HeuristicCombinedDivisorsEnding3 = CopySpanToDevice(accelerator, stream, combinedEnding3); - var combinedSquaresEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3Squares; - HeuristicCombinedDivisorSquaresEnding3 = CopySpanToDevice(accelerator, stream, combinedSquaresEnding3); + var combinedEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3; + HeuristicCombinedDivisorsEnding3 = CopySpanToDevice(accelerator, stream, combinedEnding3); + var combinedSquaresEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3Squares; + HeuristicCombinedDivisorSquaresEnding3 = CopySpanToDevice(accelerator, stream, combinedSquaresEnding3); - var combinedEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7; - HeuristicCombinedDivisorsEnding7 = CopySpanToDevice(accelerator, stream, combinedEnding7); - var combinedSquaresEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7Squares; - HeuristicCombinedDivisorSquaresEnding7 = CopySpanToDevice(accelerator, stream, combinedSquaresEnding7); + var combinedEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7; + HeuristicCombinedDivisorsEnding7 = CopySpanToDevice(accelerator, stream, combinedEnding7); + var combinedSquaresEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7Squares; + HeuristicCombinedDivisorSquaresEnding7 = CopySpanToDevice(accelerator, stream, combinedSquaresEnding7); - var combinedEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9; - HeuristicCombinedDivisorsEnding9 = CopySpanToDevice(accelerator, stream, combinedEnding9); - var combinedSquaresEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9Squares; - HeuristicCombinedDivisorSquaresEnding9 = CopySpanToDevice(accelerator, stream, combinedSquaresEnding9); - } + var combinedEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9; + HeuristicCombinedDivisorsEnding9 = CopySpanToDevice(accelerator, stream, combinedEnding9); + var combinedSquaresEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9Squares; + HeuristicCombinedDivisorSquaresEnding9 = CopySpanToDevice(accelerator, stream, combinedSquaresEnding9); } internal readonly MemoryBuffer1D HeuristicGroupADivisors; @@ -90,10 +104,7 @@ internal sealed class HeuristicGpuTables private static MemoryBuffer1D CopySpanToDevice(Accelerator accelerator, AcceleratorStream stream, in ulong[] span) { MemoryBuffer1D? buffer; - // lock (accelerator) - { - buffer = accelerator.Allocate1D(stream, span); - } + buffer = accelerator.Allocate1D(stream, span); return buffer; } @@ -101,10 +112,7 @@ internal sealed class HeuristicGpuTables private static MemoryBuffer1D CopyUintSpanToDevice(Accelerator accelerator, AcceleratorStream stream, ReadOnlySpan span) { MemoryBuffer1D? buffer; - // lock (accelerator) - { - buffer = accelerator.Allocate1D(span.Length); - } + buffer = accelerator.Allocate1D(span.Length); if (!span.IsEmpty) { diff --git a/PerfectNumbers.Core/Gpu/Accelerators/HeuristicPrimeTesterAccelerator.cs b/PerfectNumbers.Core/Gpu/Accelerators/HeuristicPrimeTesterAccelerator.cs index 30dd44e..de9e4b4 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/HeuristicPrimeTesterAccelerator.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/HeuristicPrimeTesterAccelerator.cs @@ -15,24 +15,18 @@ internal sealed class HeuristicPrimeTesterAccelerator { Accelerator[]? accelerators = AcceleratorPool.Shared.Accelerators; int acceleratorCount = accelerators.Length; - Span streams = new(new AcceleratorStream[acceleratorCount]); for (var i = 0; i < acceleratorCount; i++) { Console.WriteLine($"Preparing heuristic accelerator {i}..."); var accelerator = accelerators[i]; AcceleratorStream stream = accelerator.CreateStream(); - Accelerators.HeuristicGpuTables.EnsureStaticTables(accelerator, stream); + Accelerators.HeuristicGpuTables.EnsureStaticTables(i, stream); // _ = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.None); // KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream); // GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream); - streams[i] = stream; - } - - for (var i = 0; i < acceleratorCount; i++) - { - AcceleratorStream stream = streams[i]; stream.Synchronize(); stream.Dispose(); + accelerator.Synchronize(); Console.WriteLine($"Heuristic accelerator {i} is ready"); } } @@ -148,7 +142,7 @@ internal sealed class HeuristicPrimeTesterAccelerator // GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream); - _sharedTables = Accelerators.HeuristicGpuTables.EnsureStaticTables(accelerator, stream); + _sharedTables = Accelerators.HeuristicGpuTables.EnsureStaticTables(acceleratorIndex, stream); _heuristicDivisorTables = _sharedTables.CreateHeuristicDivisorTables(); stream.Synchronize(); diff --git a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs index 323be20..fae25c4 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -1,12 +1,13 @@ using ILGPU; using ILGPU.Runtime; +using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core.Gpu; public class GpuKernelPool { [ThreadStatic] - private static Dictionary? _pool; + private static KernelContainer[]? _pool; public static T InitOnce(ref T? target, Func valueFactory) where T : class { @@ -19,14 +20,18 @@ public class GpuKernelPool return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue; } - internal static KernelContainer GetOrAddKernels(Accelerator accelerator, AcceleratorStream stream, KernelType kernelType) + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + + + internal static KernelContainer GetOrAddKernels(int acceleratorIndex, AcceleratorStream stream, KernelType kernelType) { var pool = _pool ??= []; - if (!pool.TryGetValue(accelerator, out var kernels)) + if (pool[acceleratorIndex] is not {} kernels) { - kernels = new KernelContainer(); + kernels = new(); } + Accelerator accelerator = _accelerators[acceleratorIndex]; if (kernelType.HasFlag(KernelType.OrderKernelScan)) { InitOrderKernelScan(accelerator, kernels); @@ -64,7 +69,7 @@ public class GpuKernelPool PreloadStaticTables(accelerator, kernels, stream, kernelType); - pool[accelerator] = kernels; + pool[acceleratorIndex] = kernels; return kernels; static void InitOrderKernelScan(Accelerator accelerator, KernelContainer kernels) @@ -281,8 +286,9 @@ public class GpuKernelPool public static void Run(Action action) { // GpuPrimeWorkLimiter.Acquire(); - var accelerator = SharedGpuContext.CreateAccelerator(); - var stream = accelerator.CreateStream(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; + var stream = AcceleratorStreamPool.Rent(acceleratorIndex); action(accelerator, stream); stream.Synchronize(); stream.Dispose(); diff --git a/PerfectNumbers.Core/Gpu/InputOutputScratchBuffers.cs b/PerfectNumbers.Core/Gpu/InputOutputScratchBuffers.cs deleted file mode 100644 index 39f409a..0000000 --- a/PerfectNumbers.Core/Gpu/InputOutputScratchBuffers.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Collections.Concurrent; -using ILGPU; -using ILGPU.Runtime; - -namespace PerfectNumbers.Core.Gpu; - -public sealed class GpuInputOutputScratchBuffer - where TInput : unmanaged - where TOutput: unmanaged -{ - private readonly Accelerator _accelerator; - public MemoryBuffer1D Input { get; private set; } - public MemoryBuffer1D Output { get; private set; } - - public GpuInputOutputScratchBuffer(Accelerator accelerator,int inputCapacity, int outputCapacity) - { - _accelerator = accelerator; - // lock (accelerator) - { - Input = accelerator.Allocate1D(inputCapacity); - Output = accelerator.Allocate1D(outputCapacity); - } - } - - public void EnsureCapacity(int inputCapacity, int outputCapacity) - { - if (Input.Length >= inputCapacity && Output.Length >= outputCapacity) - { - return; - } - - Accelerator accelerator = _accelerator; - if (Input.Length >= inputCapacity && Output.Length >= outputCapacity) - { - return; - } - - // lock (accelerator) - { - if (Input.Length < inputCapacity) - { - Input.Dispose(); - Input = accelerator.Allocate1D(inputCapacity); - } - - if (Output.Length < outputCapacity) - { - Output.Dispose(); - Output = accelerator.Allocate1D(outputCapacity); - } - } - } - - public void Dispose() - { - Input.Dispose(); - Output.Dispose(); - } -} - -public static class GpuInputOutputScratchBufferPool - where TInput : unmanaged - where TOutput : unmanaged -{ - private const int WarmUpCapacity = 20480; - - [ThreadStatic] - private static Dictionary>>? _pool; - - private static Queue> GetPools(Accelerator accelerator) - { - var pool = _pool ??= []; - - if (!pool.TryGetValue(accelerator, out var bufferQueue)) - { - bufferQueue = []; - pool[accelerator] = bufferQueue; - } - - return bufferQueue; - } - - // private static readonly ConcurrentDictionary> _pools = new(); - - private const int DefaultSmallPrimeFactorSlotCount = 64; // From PrimeOrderCalculator.Gpu.cs - private const int DefaultSpecialMaxFactorCapacity = 1024; // A reasonable default, will be resized if needed - - public static GpuInputOutputScratchBuffer Rent(Accelerator accelerator, int inputCapacity, int outputCapacity) - { - var bufferQueue = GetPools(accelerator); - if (bufferQueue.TryDequeue(out var buffer)) - { - if (buffer.Input.Length < inputCapacity || buffer.Output.Length < outputCapacity) - { - // lock (accelerator) - // { - // Console.WriteLine($"Resizing GPU scratch buffer from pool ({buffer.SmallPrimeFactorPrimeSlots.Length} / {smallPrimeFactorSlotCount}), ({buffer.SpecialMaxFactors.Length}/{specialMaxFactorCapacity})"); - buffer.EnsureCapacity(inputCapacity, outputCapacity); - // } - } - - return buffer; - } - - return new GpuInputOutputScratchBuffer(accelerator, inputCapacity, outputCapacity); - } - - public static void Return(Accelerator accelerator, GpuInputOutputScratchBuffer buffer) => GetPools(accelerator).Enqueue(buffer); - - public static void WarmUp(int count, int inputCapacity, int outputCapacity) - { - // List> rented = new(count); - // for (int i = 0; i < count; i++) - // { - // rented.Add(Rent(inputCapacity, outputCapacity)); - // } - - // for (int i = 0; i < count; i++) - // { - // Return(rented[i]); - // } - - // rented.Clear(); - // rented.Capacity = 1; - } -} \ No newline at end of file diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs index c5903ce..4c9d2b0 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs @@ -27,7 +27,7 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters - KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.Pow2ModKernelScan | KernelType.IncrementalKernelScan); + KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.Pow2ModKernelScan | KernelType.IncrementalKernelScan); var pow2Kernel = kernels.Pow2Mod!; var incKernel = kernels.Incremental!; ulong step10 = (exponent.Mod10() << 1).Mod10(); diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs index 025af5e..d064387 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs @@ -21,7 +21,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters - KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.Pow2ModOrderKernelScan | KernelType.IncrementalOrderKernelScan); + KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.Pow2ModOrderKernelScan | KernelType.IncrementalOrderKernelScan); var kernel = _kernelType switch { GpuKernelType.Pow2Mod => kernels.Pow2ModOrder!, diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index 4543832..c990e50 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using ILGPU; @@ -36,7 +35,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) var stream = accelerator.CreateStream(); var kernel = GetPow2ModKernel(accelerator); var resources = RentResources(accelerator, GpuConstants.ScanBatchSize); - var gpuKernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.SmallCycles | KernelType.SmallPrimes); + var gpuKernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.SmallCycles | KernelType.SmallPrimes); var orderBuffer = resources.OrderBuffer; ulong[] orderArray = resources.OrderArray; diff --git a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs index c955265..671d3cf 100644 --- a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs +++ b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs @@ -25,16 +25,16 @@ internal static partial class PrimeOrderGpuHeuristics private static readonly ConcurrentDictionary OverflowedPrimesWide = new(); [ThreadStatic] - private static Dictionary, MontgomeryDivisorData, ArrayView1D>>? _pow2ModKernel; + private static Kernel[]? _pow2ModKernel; [ThreadStatic] - private static Dictionary, ArrayView1D, int, int, ulong, uint, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>>? _partialFactorKernel; + private static Kernel[]? _partialFactorKernel; [ThreadStatic] - private static Dictionary? _orderKernel; + private static Kernel[]? _orderKernel; [ThreadStatic] - private static Dictionary? _smallPrimeDeviceCache; + private static SmallPrimeDeviceCache[]? _smallPrimeDeviceCache; public readonly struct OrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode) { @@ -69,17 +69,6 @@ internal static partial class PrimeOrderGpuHeuristics public readonly ArrayView1D Status = status; } - private delegate void OrderKernelLauncher( - AcceleratorStream stream, - Index1D extent, - ulong prime, - OrderKernelConfig config, - MontgomeryDivisorData divisor, - ArrayView1D primes, - ArrayView1D squares, - int primeCount, - OrderKernelBuffers buffers); - private sealed class SmallPrimeDeviceCache { public MemoryBuffer1D? Primes; @@ -111,10 +100,10 @@ internal static partial class PrimeOrderGpuHeuristics s_capability = PrimeOrderGpuCapability.Default; } - private static SmallPrimeDeviceCache GetSmallPrimeDeviceCache(Accelerator accelerator, AcceleratorStream stream) + private static SmallPrimeDeviceCache GetSmallPrimeDeviceCache(int acceleratorIndex, AcceleratorStream stream) { var pool = _smallPrimeDeviceCache ??= []; - if (pool.TryGetValue(accelerator, out var cached)) + if (pool[acceleratorIndex] is {} cached) { return cached; } @@ -123,11 +112,10 @@ internal static partial class PrimeOrderGpuHeuristics ulong[] squares = PrimesGenerator.SmallPrimesPow2; MemoryBuffer1D? primeBuffer; MemoryBuffer1D? squareBuffer; - // lock (accelerator) - { - primeBuffer = accelerator.Allocate1D(primes.Length); - squareBuffer = accelerator.Allocate1D(squares.Length); - } + + var accelerator = _accelerators[acceleratorIndex]; + primeBuffer = accelerator.Allocate1D(primes.Length); + squareBuffer = accelerator.Allocate1D(squares.Length); primeBuffer.View.CopyFromCPU(stream, primes); squareBuffer.View.CopyFromCPU(stream, squares); @@ -138,25 +126,23 @@ internal static partial class PrimeOrderGpuHeuristics Count = primes.Length, }; - pool[accelerator] = cached; + pool[acceleratorIndex] = cached; return cached; } - private static Action, ArrayView1D, int, int, ulong, uint, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> GetPartialFactorKernel(Accelerator accelerator) + private static Kernel GetPartialFactorKernel(int acceleratorIndex) { var pool = _partialFactorKernel ??= []; - if (pool.TryGetValue(accelerator, out var cached)) + if (pool[acceleratorIndex] is {} cached) { return cached; } + var accelerator = _accelerators[acceleratorIndex]; var loaded = accelerator.LoadAutoGroupedStreamKernel, ArrayView1D, int, int, ulong, uint, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(PartialFactorKernel); - var kernel = KernelUtil.GetKernel(loaded); - - cached = kernel.CreateLauncherDelegate, ArrayView1D, int, int, ulong, uint, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>>(); - - pool[accelerator] = cached; + cached = KernelUtil.GetKernel(loaded); + pool[acceleratorIndex] = cached; return cached; } @@ -226,7 +212,7 @@ internal static partial class PrimeOrderGpuHeuristics Accelerator accelerator = _accelerators[acceleratorIndex]; AcceleratorStream stream = accelerator.CreateStream(); - SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator, stream); + SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(acceleratorIndex, stream); // TODO: We should create / reallocate these buffer only once or if the new required length is bigger than capacity. MemoryBuffer1D? factorBuffer = accelerator.Allocate1D(primeTargets.Length); @@ -242,11 +228,12 @@ internal static partial class PrimeOrderGpuHeuristics // remainingBuffer.MemSetToZero(stream); // fullyFactoredBuffer.MemSetToZero(stream); - var partialFactorKernel = GetPartialFactorKernel(accelerator); + var partialFactorKernel = GetPartialFactorKernel(acceleratorIndex); - partialFactorKernel( + partialFactorKernel.Launch( stream, 1, + 1, cache.Primes!.View, cache.Squares!.View, cache.Count, @@ -388,7 +375,7 @@ internal static partial class PrimeOrderGpuHeuristics int acceleratorIndex = AcceleratorPool.Shared.Rent(); Accelerator accelerator = _accelerators[acceleratorIndex]; AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); - SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator, stream); + SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(acceleratorIndex, stream); // TODO: These buffers should be created reallocated once and assigned to an accelerator. Callers should use their own // thread static cache to prevent other threads from using taking accelerators with pre-allocated buffers if they don't @@ -447,10 +434,11 @@ internal static partial class PrimeOrderGpuHeuristics resultBuffer.View, statusBuffer.View); - var orderKernel = GetOrderKernel(accelerator); - orderKernel( + var orderKernel = GetOrderKernel(acceleratorIndex); + orderKernel.Launch( stream, 1, + 1, prime, kernelConfig, divisorData, @@ -497,19 +485,20 @@ internal static partial class PrimeOrderGpuHeuristics return order != 0UL; } - private static OrderKernelLauncher GetOrderKernel(Accelerator accelerator) + private static Kernel GetOrderKernel(int acceleratorIndex) { var pool = _orderKernel ??= []; - if (pool.TryGetValue(accelerator, out var cached)) + if (pool[acceleratorIndex] is {} cached) { return cached; } + var accelerator = _accelerators[acceleratorIndex]; var loaded = accelerator.LoadAutoGroupedStreamKernel, ArrayView1D, int, OrderKernelBuffers>(CalculateOrderKernel); + var kernel = KernelUtil.GetKernel(loaded); - cached = kernel.CreateLauncherDelegate(); - pool[accelerator] = cached; - return cached; + pool[acceleratorIndex] = kernel; + return kernel; } private static GpuPow2ModStatus TryPow2ModBatchInternal(ReadOnlySpan exponents, UInt128 prime, Span remainders) @@ -576,10 +565,10 @@ internal static partial class PrimeOrderGpuHeuristics // Pow2Mod kernel always assigns the required output elements so we don't need to worry about clearing these. // remainderBuffer.MemSetToZero(stream); - var pow2ModKernel = GetPow2ModKernel(accelerator); + var pow2ModKernel = GetPow2ModKernel(acceleratorIndex); try { - pow2ModKernel(stream, exponents.Length, exponentBuffer.View, divisorData, remainderBuffer.View); + pow2ModKernel.Launch(stream, 1, exponents.Length, exponentBuffer.View, divisorData, remainderBuffer.View); remainderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(results), exponents.Length); stream.Synchronize(); @@ -684,21 +673,19 @@ internal static partial class PrimeOrderGpuHeuristics return ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisorData, exponent); } - private static Action, MontgomeryDivisorData, ArrayView1D> GetPow2ModKernel(Accelerator accelerator) + private static Kernel GetPow2ModKernel(int acceleratorIndex) { var pool = _pow2ModKernel ??= []; - if (pool.TryGetValue(accelerator, out var cached)) + if (pool[acceleratorIndex] is {} cached) { return cached; } - var loaded = accelerator.LoadAutoGroupedStreamKernel, MontgomeryDivisorData, ArrayView1D>(Pow2ModKernel); + var accelerator = _accelerators[acceleratorIndex]; + var loaded = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel, MontgomeryDivisorData, ArrayView1D>(Pow2ModKernel)); - var kernel = KernelUtil.GetKernel(loaded); - - cached = kernel.CreateLauncherDelegate, MontgomeryDivisorData, ArrayView1D>>(); - pool[accelerator] = cached; - return cached; + pool[acceleratorIndex] = loaded; + return loaded; } internal readonly record struct PrimeOrderGpuCapability(int ModulusBits, int ExponentBits) diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 75e85c1..271b56f 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -9,6 +9,7 @@ using PerfectNumbers.Core.Gpu; using ILGPU; using ILGPU.Algorithms; using ILGPU.Runtime; +using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core; @@ -789,7 +790,7 @@ public class MersenneDivisorCycles } [ThreadStatic] - private static Dictionary, ArrayView1D>>? _divisorCycleKernel; + private static Kernel[]? _divisorCycleKernel; private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; @@ -809,7 +810,7 @@ public class MersenneDivisorCycles // GpuPrimeWorkLimiter.Acquire(); var acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = _accelerators[acceleratorIndex]; - var stream = accelerator.CreateStream(); + var stream = AcceleratorStreamPool.Rent(acceleratorIndex); using Stream outputStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, BufferSize10M, useAsync: true); if (nextPosition > 0L) @@ -838,7 +839,7 @@ public class MersenneDivisorCycles skipCount--; } - var divisorCycleKernel = GetDivisorCycleKernel(accelerator); + var divisorCycleKernel = GetDivisorCycleKernel(acceleratorIndex); while (start <= maxDivisor) { end = Math.Min(start + batchSizeUL - 1UL, maxDivisor); @@ -865,8 +866,9 @@ public class MersenneDivisorCycles bufferCycle = accelerator.Allocate1D(idx); } - divisorCycleKernel( + divisorCycleKernel.Launch( stream, + 1, idx, bufferDiv.View, bufferCycle.View); @@ -896,31 +898,28 @@ public class MersenneDivisorCycles start = end + 1UL; } - stream.Dispose(); + AcceleratorStreamPool.Return(acceleratorIndex, stream); pool.Return(divisors, clearArray: false); pool.Return(outCycles, clearArray: false); // GpuPrimeWorkLimiter.Release(); } - private static Action, ArrayView1D> GetDivisorCycleKernel(Accelerator accelerator) + private static Kernel GetDivisorCycleKernel(int acceleratorIndex) { var pool = _divisorCycleKernel ??= []; - if (pool.TryGetValue(accelerator, out var cached)) + if (pool[acceleratorIndex] is {} cached) { return cached; } - var loaded = accelerator.LoadAutoGroupedStreamKernel< + var loaded = KernelUtil.GetKernel(_accelerators[acceleratorIndex].LoadAutoGroupedStreamKernel< Index1D, ArrayView1D, - ArrayView1D>(DivisorCycleKernels.GpuDivisorCycleKernel); + ArrayView1D>(DivisorCycleKernels.GpuDivisorCycleKernel)); - var kernel = KernelUtil.GetKernel(loaded); - - cached = kernel.CreateLauncherDelegate, ArrayView1D>>(); - pool[accelerator] = cached; - return cached; + pool[acceleratorIndex] = loaded; + return loaded; } public static ulong CalculateCycleLengthGpu(ulong divisor) => DivisorCycleKernels.CalculateCycleLengthGpu(divisor); diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index d82c012..2719d3e 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -6,6 +6,7 @@ using ILGPU.Runtime; using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Gpu; using UInt128 = System.UInt128; +using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core; @@ -152,8 +153,8 @@ public sealed class MersenneNumberTester( // GpuPrimeWorkLimiter.Acquire(); var acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = _accelerators[acceleratorIndex]; - var stream = accelerator.CreateStream(); - var orderKernel = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.OrderKernelScan).Order!; + var stream = AcceleratorStreamPool.Rent(acceleratorIndex); + var orderKernel = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.OrderKernelScan).Order!; // Guard long-running kernels by chunking the warm-up across batches. // Reuse the same ScanBatchSize knob used for scanning. @@ -203,7 +204,7 @@ public sealed class MersenneNumberTester( qBuffer.Dispose(); } - stream.Dispose(); + AcceleratorStreamPool.Return(acceleratorIndex,stream); // GpuPrimeWorkLimiter.Release(); uInt128Pool.Return(qs); } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs index 53f1850..38d45f0 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs @@ -70,7 +70,7 @@ internal static partial class PrimeOrderCalculator // GpuPrimeWorkLimiter.Acquire(); var stream = AcceleratorStreamPool.Rent(acceleratorIndex); - KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.SmallPrimeFactorKernelScan); + KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.SmallPrimeFactorKernelScan); SmallPrimeFactorViews tables = GpuKernelPool.GetSmallPrimeFactorTables(kernels); var kernel = kernels.SmallPrimeFactor!; @@ -141,7 +141,7 @@ internal static partial class PrimeOrderCalculator Accelerator accelerator = gpu.Accelerator; // GpuPrimeWorkLimiter.Acquire(); var stream = AcceleratorStreamPool.Rent(acceleratorIndex); - var kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.EvaluateSpecialMaxCandidatesKernel); + var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.EvaluateSpecialMaxCandidatesKernel); ArrayView1D specialMaxFactorsView = gpu.SpecialMaxFactors.View; specialMaxFactorsView.SubView(0, factorCount).CopyFromCPU(stream, ref MemoryMarshal.GetReference(factors), factorCount); diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 1fabf41..574319b 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -269,9 +269,6 @@ public sealed class PrimeTester return BinaryGcd(n, m) != 1UL; } - [ThreadStatic] - private static readonly Dictionary, ArrayView>>? _kernel; - internal static void SharesFactorWithMaxExponentBatch(ReadOnlySpan values, Span results) { // TODO: Route this batch helper through the shared GPU kernel pool from