diff --git a/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs b/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs index 6eda845..3e9d2de 100644 --- a/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs @@ -18,19 +18,19 @@ public class GpuPrimeLimiterTests var startedSecond = new TaskCompletionSource(); var sw = new Stopwatch(); - var first = GpuPrimeWorkLimiter.Acquire(); + GpuPrimeWorkLimiter.Acquire(); sw.Start(); var secondTask = Task.Run(() => { - var second = GpuPrimeWorkLimiter.Acquire(); + GpuPrimeWorkLimiter.Acquire(); try { startedSecond.TrySetResult(true); } finally { - second.Dispose(); + GpuPrimeWorkLimiter.Release(); } }); @@ -43,7 +43,7 @@ public class GpuPrimeLimiterTests finally { // Release first after the initial wait so the second task can proceed. - first.Dispose(); + GpuPrimeWorkLimiter.Release(); } await secondTask.WaitAsync(TimeSpan.FromSeconds(1)); diff --git a/PerfectNumbers.Core/Gpu/GpuContextPool.cs b/PerfectNumbers.Core/Gpu/GpuContextPool.cs index 00cb47f..ae11bda 100644 --- a/PerfectNumbers.Core/Gpu/GpuContextPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuContextPool.cs @@ -14,11 +14,12 @@ public static class GpuContextPool public readonly Context Context; public readonly Accelerator Accelerator; - public PooledContext() + public PooledContext(KernelContainer kernels) { Context = SharedGpuContext.Context; Accelerator = SharedGpuContext.Accelerator; - GpuStaticTableInitializer.EnsureStaticTables(Accelerator); + GpuStaticTableInitializer.EnsureStaticTables(kernels, Accelerator); + kernels.Dispose(); // NOTE: Avoid loading/compiling any kernel here to prevent implicit // CL stream/queue creation during accelerator construction. // Some OpenCL drivers are fragile when a queue is created immediately @@ -52,7 +53,7 @@ public static class GpuContextPool } // Create a new accelerator when the pool does not have one available. - return new GpuContextLease(new PooledContext()); + return new GpuContextLease(new PooledContext(GpuKernelPool.GetKernels())); } @@ -70,7 +71,8 @@ public static class GpuContextPool PooledContext context; for (int i = 0; i < toCreate; i++) { - context = new PooledContext(); + KernelContainer kernels = GpuKernelPool.GetKernels(); + context = new PooledContext(kernels); contexts[i] = context; pool.Enqueue(context); } diff --git a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs index 9b84a9d..1db24b1 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs @@ -11,25 +11,24 @@ public sealed class GpuKernelLease { private static readonly ConcurrentQueue Pool = new(); - private Action? _releaseLimiter; private GpuContextLease? _gpu; - private KernelContainer? _kernels; + public KernelContainer Kernels; private AcceleratorStream? _stream; - private GpuKernelLease() - { - } + private GpuKernelLease(KernelContainer kernels) + { + Kernels = kernels; + } - internal static GpuKernelLease Rent(GpuPrimeWorkLimiter.Lease limiter, GpuContextLease gpu, KernelContainer kernels) + internal static GpuKernelLease Rent(GpuContextLease gpu, KernelContainer kernels) { if (!Pool.TryDequeue(out var lease)) { - lease = new GpuKernelLease(); + lease = new GpuKernelLease(kernels); } - lease._releaseLimiter = limiter.Dispose; lease._gpu = gpu; - lease._kernels = kernels; + lease.Kernels = kernels; lease._stream = null; return lease; } @@ -57,7 +56,7 @@ public sealed class GpuKernelLease get { var accel = Accelerator; // avoid capturing 'this' in lambda - return KernelContainer.InitOnce(ref _kernels!.Order, () => + return KernelContainer.InitOnce(ref Kernels!.Order, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView>(OrderKernels.OrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -71,7 +70,7 @@ public sealed class GpuKernelLease get { var accel = Accelerator; - return KernelContainer.InitOnce(ref _kernels!.Pow2Mod, () => + return KernelContainer.InitOnce(ref Kernels!.Pow2Mod, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(Pow2ModKernels.Pow2ModKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -85,7 +84,7 @@ public sealed class GpuKernelLease get { var accel = Accelerator; - return KernelContainer.InitOnce(ref _kernels!.Incremental, () => + return KernelContainer.InitOnce(ref Kernels!.Incremental, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernels.IncrementalKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -99,7 +98,7 @@ public sealed class GpuKernelLease get { var accel = Accelerator; - return KernelContainer.InitOnce(ref _kernels!.IncrementalOrder, () => + return KernelContainer.InitOnce(ref Kernels!.IncrementalOrder, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernels.IncrementalOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -113,7 +112,7 @@ public sealed class GpuKernelLease get { var accel = Accelerator; - return KernelContainer.InitOnce(ref _kernels!.Pow2ModOrder, () => + return KernelContainer.InitOnce(ref Kernels!.Pow2ModOrder, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(Pow2ModKernels.Pow2ModOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -122,16 +121,16 @@ public sealed class GpuKernelLease } } - public Action, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D> SpecialMaxKernel + public Action, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D> SpecialMaxKernel { get { var accel = Accelerator; - return KernelContainer.InitOnce(ref _kernels!.SpecialMax, () => + return KernelContainer.InitOnce(ref Kernels!.SpecialMax, () => { - var loaded = accel.LoadAutoGroupedStreamKernel, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel); + var loaded = accel.LoadAutoGroupedStreamKernel, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel); var kernel = KernelUtil.GetKernel(loaded); - return kernel.CreateLauncherDelegate, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>>(); + return kernel.CreateLauncherDelegate, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>>(); }); } } @@ -141,7 +140,7 @@ public sealed class GpuKernelLease get { var accel = Accelerator; - return KernelContainer.InitOnce(ref _kernels!.SmallPrimeFactor, () => + return KernelContainer.InitOnce(ref Kernels!.SmallPrimeFactor, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -158,10 +157,8 @@ public sealed class GpuKernelLease _gpu?.Dispose(); _gpu = null; - _releaseLimiter?.Invoke(); - _releaseLimiter = null; - - _kernels = null; + Kernels?.Dispose(); + GpuPrimeWorkLimiter.Release(); Pool.Enqueue(this); } diff --git a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs index 9e65ef3..365b4d6 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -8,368 +8,315 @@ namespace PerfectNumbers.Core.Gpu; public readonly struct ResidueAutomatonArgs { - public readonly ulong Q0M10; - public readonly ulong Step10; - public readonly ulong Q0M8; - public readonly ulong Step8; - public readonly ulong Q0M3; - public readonly ulong Step3; - public readonly ulong Q0M5; - public readonly ulong Step5; + public readonly ulong Q0M10; + public readonly ulong Step10; + public readonly ulong Q0M8; + public readonly ulong Step8; + public readonly ulong Q0M3; + public readonly ulong Step3; + public readonly ulong Q0M5; + public readonly ulong Step5; - public ResidueAutomatonArgs(ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5) - { - Q0M10 = q0m10; Step10 = step10; Q0M8 = q0m8; Step8 = step8; Q0M3 = q0m3; Step3 = step3; Q0M5 = q0m5; Step5 = step5; - } + public ResidueAutomatonArgs(ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5) + { + Q0M10 = q0m10; Step10 = step10; Q0M8 = q0m8; Step8 = step8; Q0M3 = q0m3; Step3 = step3; Q0M5 = q0m5; Step5 = step5; + } } public sealed class KernelContainer { - // Serializes first-time initialization of kernels/buffers per accelerator. - public Action, ArrayView>? Order; - public Action, ArrayView1D>? Incremental; - public Action, ArrayView1D, - ArrayView1D, ArrayView1D, - ArrayView1D, ArrayView1D>? Pow2Mod; - public Action, ArrayView1D>? IncrementalOrder; - public Action, ArrayView1D>? Pow2ModOrder; - public Action, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>? SmallPrimeFactor; - public Action, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>? SpecialMax; + // Serializes first-time initialization of kernels/buffers per accelerator. + public Action, ArrayView>? Order; + public Action, ArrayView1D>? Incremental; + public Action, ArrayView1D, + ArrayView1D, ArrayView1D, + ArrayView1D, ArrayView1D>? Pow2Mod; + public Action, ArrayView1D>? IncrementalOrder; + public Action, ArrayView1D>? Pow2ModOrder; + public Action, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>? SmallPrimeFactor; + public Action, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>? SpecialMax; - // Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length. - public MemoryBuffer1D? SmallCycles; - public MemoryBuffer1D? SmallPrimeFactorsPrimes; - public MemoryBuffer1D? SmallPrimeFactorsSquares; - public MemoryBuffer1D? SmallPrimeFactorPrimeSlots; - public MemoryBuffer1D? SmallPrimeFactorExponentSlots; - public MemoryBuffer1D? SmallPrimeFactorCountSlot; - public MemoryBuffer1D? SmallPrimeFactorRemainingSlot; - public MemoryBuffer1D? SpecialMaxFactors; - public MemoryBuffer1D? SpecialMaxCandidates; - public MemoryBuffer1D? SpecialMaxResult; - public MemoryBuffer1D? SmallPrimesLastOne; - public MemoryBuffer1D? SmallPrimesPow2LastOne; - public MemoryBuffer1D? SmallPrimesLastSeven; - public MemoryBuffer1D? SmallPrimesPow2LastSeven; + // Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length. + public MemoryBuffer1D? SmallCycles; + public MemoryBuffer1D? SmallPrimeFactorsPrimes; + public MemoryBuffer1D? SmallPrimeFactorsSquares; + public MemoryBuffer1D? SmallPrimeFactorPrimeSlots; + public MemoryBuffer1D? SmallPrimeFactorExponentSlots; + public MemoryBuffer1D? SmallPrimeFactorCountSlot; + public MemoryBuffer1D? SmallPrimeFactorRemainingSlot; + public MemoryBuffer1D? SpecialMaxFactors; + public MemoryBuffer1D? SpecialMaxCandidates; + public MemoryBuffer1D? SpecialMaxResult; + public MemoryBuffer1D? SmallPrimesLastOne; + public MemoryBuffer1D? SmallPrimesPow2LastOne; + public MemoryBuffer1D? SmallPrimesLastSeven; + public MemoryBuffer1D? SmallPrimesPow2LastSeven; - public static T InitOnce(ref T? slot, Func factory) where T : class - { - var current = Volatile.Read(ref slot); - if (current is not null) - { - return current; - } + public static T InitOnce(ref T? slot, Func factory) where T : class + { + var current = Volatile.Read(ref slot); + if (current is not null) + { + return current; + } - current = Volatile.Read(ref slot); - if (current is null) - { - current = factory(); - Volatile.Write(ref slot, current); - } - return current; - } + current = Volatile.Read(ref slot); + if (current is null) + { + current = factory(); + Volatile.Write(ref slot, current); + } + return current; + } + + public void Dispose() => GpuKernelPool.Return(this); } public readonly struct ResiduePrimeViews( - ArrayView1D lastOne, - ArrayView1D lastSeven, - ArrayView1D lastOnePow2, - ArrayView1D lastSevenPow2) + ArrayView1D lastOne, + ArrayView1D lastSeven, + ArrayView1D lastOnePow2, + ArrayView1D lastSevenPow2) { - public readonly ArrayView1D LastOne = lastOne; - public readonly ArrayView1D LastSeven = lastSeven; - public readonly ArrayView1D LastOnePow2 = lastOnePow2; - public readonly ArrayView1D LastSevenPow2 = lastSevenPow2; + public readonly ArrayView1D LastOne = lastOne; + public readonly ArrayView1D LastSeven = lastSeven; + public readonly ArrayView1D LastOnePow2 = lastOnePow2; + public readonly ArrayView1D LastSevenPow2 = lastSevenPow2; } -public readonly struct SmallPrimeFactorTables +public readonly struct SmallPrimeFactorTables( + MemoryBuffer1D primes, + MemoryBuffer1D squares, + int count) { - public readonly MemoryBuffer1D Primes; - public readonly MemoryBuffer1D Squares; - public readonly int Count; + public readonly MemoryBuffer1D Primes = primes; + public readonly MemoryBuffer1D Squares = squares; + public readonly int Count = count; - public SmallPrimeFactorTables( - MemoryBuffer1D primes, - MemoryBuffer1D squares, - int count) - { - Primes = primes; - Squares = squares; - Count = count; - } - - public ArrayView1D PrimesView => Primes.View; - public ArrayView1D SquaresView => Squares.View; + public ArrayView1D PrimesView => Primes.View; + public ArrayView1D SquaresView => Squares.View; } -public readonly struct SmallPrimeFactorScratch +public readonly struct SmallPrimeFactorScratch( + MemoryBuffer1D primeSlots, + MemoryBuffer1D exponentSlots, + MemoryBuffer1D countSlot, + MemoryBuffer1D remainingSlot) { - public readonly MemoryBuffer1D PrimeSlots; - public readonly MemoryBuffer1D ExponentSlots; - public readonly MemoryBuffer1D CountSlot; - public readonly MemoryBuffer1D RemainingSlot; + public readonly MemoryBuffer1D PrimeSlots = primeSlots; + public readonly MemoryBuffer1D ExponentSlots = exponentSlots; + public readonly MemoryBuffer1D CountSlot = countSlot; + public readonly MemoryBuffer1D RemainingSlot = remainingSlot; - public SmallPrimeFactorScratch( - MemoryBuffer1D primeSlots, - MemoryBuffer1D exponentSlots, - MemoryBuffer1D countSlot, - MemoryBuffer1D remainingSlot) - { - PrimeSlots = primeSlots; - ExponentSlots = exponentSlots; - CountSlot = countSlot; - RemainingSlot = remainingSlot; - } - - public void Clear() - { - PrimeSlots.MemSetToZero(); - ExponentSlots.MemSetToZero(); - CountSlot.MemSetToZero(); - RemainingSlot.MemSetToZero(); - } + public void Clear() + { + PrimeSlots.MemSetToZero(); + ExponentSlots.MemSetToZero(); + CountSlot.MemSetToZero(); + RemainingSlot.MemSetToZero(); + } } -public readonly struct SpecialMaxScratch +public readonly struct SpecialMaxScratch( + MemoryBuffer1D factorValues, + MemoryBuffer1D candidateValues, + MemoryBuffer1D resultSlot) { - public readonly MemoryBuffer1D FactorValues; - public readonly MemoryBuffer1D CandidateValues; - public readonly MemoryBuffer1D ResultSlot; + public readonly MemoryBuffer1D FactorValues = factorValues; + public readonly MemoryBuffer1D CandidateValues = candidateValues; + public readonly MemoryBuffer1D ResultSlot = resultSlot; - public SpecialMaxScratch( - MemoryBuffer1D factorValues, - MemoryBuffer1D candidateValues, - MemoryBuffer1D resultSlot) - { - FactorValues = factorValues; - CandidateValues = candidateValues; - ResultSlot = resultSlot; - } - - public ArrayView1D FactorsView => FactorValues.View; - - public ArrayView1D CandidatesView => CandidateValues.View; - - public ArrayView1D ResultView => ResultSlot.View; + public ArrayView1D FactorsView => FactorValues.View; + public ArrayView1D CandidatesView => CandidateValues.View; + public ArrayView1D ResultView => ResultSlot.View; } public class GpuKernelPool { - private static readonly ConcurrentDictionary KernelCache = new(); // TODO: Replace this concurrent map with a simple accelerator-indexed lookup once kernel launchers are prewarmed during startup so we can drop the thread-safe wrapper entirely. + private static readonly ConcurrentQueue KernelCache = new(); // TODO: Replace this concurrent map with a simple accelerator-indexed lookup once kernel launchers are prewarmed during startup so we can drop the thread-safe wrapper entirely. - private static KernelContainer GetKernels(Accelerator accelerator) - { - return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer()); - } + public static KernelContainer GetKernels() => KernelCache.TryDequeue(out var container) + ? container + : new KernelContainer(); - internal static void PreloadStaticTables(Accelerator accelerator) - { - EnsureSmallCyclesOnDevice(accelerator); - _ = EnsureSmallPrimesOnDevice(accelerator); - _ = EnsureSmallPrimeFactorTables(accelerator); - } + public static void Return(KernelContainer container) => KernelCache.Enqueue(container); - // Ensures the small cycles table is uploaded to the device for the given accelerator. - // Returns the ArrayView to pass into kernels (when kernels are extended to accept it). - public static ArrayView1D EnsureSmallCyclesOnDevice(Accelerator accelerator) - { - var kernels = GetKernels(accelerator); - if (kernels.SmallCycles is { } buffer) - { - return buffer.View; - } + internal static void PreloadStaticTables(KernelContainer kernels, Accelerator accelerator) + { + EnsureSmallCyclesOnDevice(kernels, accelerator); + EnsureSmallPrimesOnDevice(kernels, accelerator); + EnsureSmallPrimeFactorTables(kernels, accelerator); + } - // Ensure single upload per accelerator even if multiple threads race here. - lock (kernels) // TODO: Remove this lock by pre-uploading the immutable small-cycle snapshot during initialization; once no mutation happens at runtime, the pool must expose a simple reference without synchronization. - { - if (kernels.SmallCycles is { } existing) - { - return existing.View; - } + // Ensures the small cycles table is uploaded to the device for the given accelerator. + // Returns the ArrayView to pass into kernels (when kernels are extended to accept it). + public static ArrayView1D EnsureSmallCyclesOnDevice(KernelContainer kernels, Accelerator accelerator) + { + if (kernels.SmallCycles is { } buffer) + { + return buffer.View; + } - var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot. - var device = accelerator.Allocate1D(host.Length); - device.View.CopyFromCPU(host); - kernels.SmallCycles = device; - return device.View; - } - } + var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot. + var device = accelerator.Allocate1D(host.Length); + device.View.CopyFromCPU(host); + kernels.SmallCycles = device; + return device.View; + } - public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator) - { - var kernels = GetKernels(accelerator); - if (kernels.SmallPrimesLastOne is { } lastOne && - kernels.SmallPrimesLastSeven is { } lastSeven && - kernels.SmallPrimesPow2LastOne is { } lastOnePow2 && - kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2) - { - return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View); - } + public static ResiduePrimeViews EnsureSmallPrimesOnDevice(KernelContainer kernels, Accelerator accelerator) + { + if (kernels.SmallPrimesLastOne is { } lastOne && + kernels.SmallPrimesLastSeven is { } lastSeven && + kernels.SmallPrimesPow2LastOne is { } lastOnePow2 && + kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2) + { + return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View); + } - lock (kernels) // TODO: Inline these small-prime uploads into startup initialization alongside the small-cycle snapshot so we can drop runtime locking and keep the GPU pool free of synchronization. - { - if (kernels.SmallPrimesLastOne is { } existingLastOne && - kernels.SmallPrimesLastSeven is { } existingLastSeven && - kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 && - kernels.SmallPrimesPow2LastSeven is { } existingLastSevenPow2) - { - return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View); - } + if (kernels.SmallPrimesLastOne is { } existingLastOne && + kernels.SmallPrimesLastSeven is { } existingLastSeven && + kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 && + kernels.SmallPrimesPow2LastSeven is { } existingLastSevenPow2) + { + return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View); + } - var hostLastOne = PrimesGenerator.SmallPrimesLastOne; - var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven; - var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; - var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; + var hostLastOne = PrimesGenerator.SmallPrimesLastOne; + var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven; + var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; + var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; - var deviceLastOne = accelerator.Allocate1D(hostLastOne.Length); - deviceLastOne.View.CopyFromCPU(hostLastOne); - var deviceLastSeven = accelerator.Allocate1D(hostLastSeven.Length); - deviceLastSeven.View.CopyFromCPU(hostLastSeven); - var deviceLastOnePow2 = accelerator.Allocate1D(hostLastOnePow2.Length); - deviceLastOnePow2.View.CopyFromCPU(hostLastOnePow2); - var deviceLastSevenPow2 = accelerator.Allocate1D(hostLastSevenPow2.Length); - deviceLastSevenPow2.View.CopyFromCPU(hostLastSevenPow2); + var deviceLastOne = accelerator.Allocate1D(hostLastOne.Length); + deviceLastOne.View.CopyFromCPU(hostLastOne); + var deviceLastSeven = accelerator.Allocate1D(hostLastSeven.Length); + deviceLastSeven.View.CopyFromCPU(hostLastSeven); + var deviceLastOnePow2 = accelerator.Allocate1D(hostLastOnePow2.Length); + deviceLastOnePow2.View.CopyFromCPU(hostLastOnePow2); + var deviceLastSevenPow2 = accelerator.Allocate1D(hostLastSevenPow2.Length); + deviceLastSevenPow2.View.CopyFromCPU(hostLastSevenPow2); - kernels.SmallPrimesLastOne = deviceLastOne; - kernels.SmallPrimesLastSeven = deviceLastSeven; - kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; - kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; + kernels.SmallPrimesLastOne = deviceLastOne; + kernels.SmallPrimesLastSeven = deviceLastSeven; + kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; + kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; - return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); - } - } + return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); + } - public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(Accelerator accelerator) - { - var kernels = GetKernels(accelerator); - if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer) - { - return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length); - } + public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(KernelContainer kernels, Accelerator accelerator) + { + if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer) + { + return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length); + } - lock (kernels) - { - if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares) - { - return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length); - } + if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares) + { + return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length); + } - var hostPrimes = PrimesGenerator.SmallPrimes; - var hostSquares = PrimesGenerator.SmallPrimesPow2; + var hostPrimes = PrimesGenerator.SmallPrimes; + var hostSquares = PrimesGenerator.SmallPrimesPow2; - var devicePrimes = accelerator.Allocate1D(hostPrimes.Length); - devicePrimes.View.CopyFromCPU(hostPrimes); - var deviceSquares = accelerator.Allocate1D(hostSquares.Length); - deviceSquares.View.CopyFromCPU(hostSquares); + var devicePrimes = accelerator.Allocate1D(hostPrimes.Length); + devicePrimes.View.CopyFromCPU(hostPrimes); + var deviceSquares = accelerator.Allocate1D(hostSquares.Length); + deviceSquares.View.CopyFromCPU(hostSquares); - kernels.SmallPrimeFactorsPrimes = devicePrimes; - kernels.SmallPrimeFactorsSquares = deviceSquares; + kernels.SmallPrimeFactorsPrimes = devicePrimes; + kernels.SmallPrimeFactorsSquares = deviceSquares; - return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); - } - } + return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); + } - public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(Accelerator accelerator, int slotCount) - { - var kernels = GetKernels(accelerator); - lock (kernels) - { - MemoryBuffer1D? primeSlots = kernels.SmallPrimeFactorPrimeSlots; - if (primeSlots is null || primeSlots.Length < slotCount) - { - primeSlots?.Dispose(); - primeSlots = accelerator.Allocate1D(slotCount); - kernels.SmallPrimeFactorPrimeSlots = primeSlots; - } + public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(KernelContainer kernels, Accelerator accelerator, int slotCount) + { + MemoryBuffer1D? primeSlots = kernels.SmallPrimeFactorPrimeSlots; + if (primeSlots is null || primeSlots.Length < slotCount) + { + primeSlots?.Dispose(); + primeSlots = accelerator.Allocate1D(slotCount); + kernels.SmallPrimeFactorPrimeSlots = primeSlots; + } - MemoryBuffer1D? exponentSlots = kernels.SmallPrimeFactorExponentSlots; - if (exponentSlots is null || exponentSlots.Length < slotCount) - { - exponentSlots?.Dispose(); - exponentSlots = accelerator.Allocate1D(slotCount); - kernels.SmallPrimeFactorExponentSlots = exponentSlots; - } + MemoryBuffer1D? exponentSlots = kernels.SmallPrimeFactorExponentSlots; + if (exponentSlots is null || exponentSlots.Length < slotCount) + { + exponentSlots?.Dispose(); + exponentSlots = accelerator.Allocate1D(slotCount); + kernels.SmallPrimeFactorExponentSlots = exponentSlots; + } - MemoryBuffer1D? countSlot = kernels.SmallPrimeFactorCountSlot; - if (countSlot is null) - { - countSlot = accelerator.Allocate1D(1); - kernels.SmallPrimeFactorCountSlot = countSlot; - } + MemoryBuffer1D? countSlot = kernels.SmallPrimeFactorCountSlot; + if (countSlot is null) + { + countSlot = accelerator.Allocate1D(1); + kernels.SmallPrimeFactorCountSlot = countSlot; + } - MemoryBuffer1D? remainingSlot = kernels.SmallPrimeFactorRemainingSlot; - if (remainingSlot is null) - { - remainingSlot = accelerator.Allocate1D(1); - kernels.SmallPrimeFactorRemainingSlot = remainingSlot; - } + MemoryBuffer1D? remainingSlot = kernels.SmallPrimeFactorRemainingSlot; + if (remainingSlot is null) + { + remainingSlot = accelerator.Allocate1D(1); + kernels.SmallPrimeFactorRemainingSlot = remainingSlot; + } - return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!); - } - } + return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!); + } - public static SpecialMaxScratch EnsureSpecialMaxScratch(Accelerator accelerator, int factorCapacity) - { - if (factorCapacity <= 0) - { - factorCapacity = 1; - } + public static SpecialMaxScratch EnsureSpecialMaxScratch(KernelContainer kernels, Accelerator accelerator, int factorCapacity) + { + MemoryBuffer1D? factorValues = kernels.SpecialMaxFactors; + if (factorValues is null || factorValues.Length < factorCapacity) + { + factorValues?.Dispose(); + factorValues = accelerator.Allocate1D(factorCapacity); + kernels.SpecialMaxFactors = factorValues; + } - var kernels = GetKernels(accelerator); - lock (kernels) - { - MemoryBuffer1D? factorValues = kernels.SpecialMaxFactors; - if (factorValues is null || factorValues.Length < factorCapacity) - { - factorValues?.Dispose(); - factorValues = accelerator.Allocate1D(factorCapacity); - kernels.SpecialMaxFactors = factorValues; - } + MemoryBuffer1D? candidateValues = kernels.SpecialMaxCandidates; + if (candidateValues is null || candidateValues.Length < factorCapacity) + { + candidateValues?.Dispose(); + candidateValues = accelerator.Allocate1D(factorCapacity); + kernels.SpecialMaxCandidates = candidateValues; + } - MemoryBuffer1D? candidateValues = kernels.SpecialMaxCandidates; - if (candidateValues is null || candidateValues.Length < factorCapacity) - { - candidateValues?.Dispose(); - candidateValues = accelerator.Allocate1D(factorCapacity); - kernels.SpecialMaxCandidates = candidateValues; - } + MemoryBuffer1D? resultSlot = kernels.SpecialMaxResult; + if (resultSlot is null) + { + resultSlot = accelerator.Allocate1D(1); + kernels.SpecialMaxResult = resultSlot; + } - MemoryBuffer1D? resultSlot = kernels.SpecialMaxResult; - if (resultSlot is null) - { - resultSlot = accelerator.Allocate1D(1); - kernels.SpecialMaxResult = resultSlot; - } + return new SpecialMaxScratch(factorValues!, candidateValues!, resultSlot!); + } - return new SpecialMaxScratch(factorValues!, candidateValues!, resultSlot!); - } - } + public static GpuKernelLease GetKernel(bool useGpuOrder) + { + GpuPrimeWorkLimiter.Acquire(); + var gpu = Rent(); + var kernels = GetKernels(); + return GpuKernelLease.Rent(gpu, kernels); + } - public static GpuKernelLease GetKernel(bool useGpuOrder) - { - var limiter = GpuPrimeWorkLimiter.Acquire(); - var gpu = Rent(); - var accelerator = gpu.Accelerator; - var kernels = GetKernels(accelerator); - return GpuKernelLease.Rent(limiter, gpu, kernels); - } + /// + /// Runs a GPU action with an acquired accelerator and stream. + /// + /// Action to run with (Accelerator, Stream). + public static void Run(Action action) + { + var lease = GetKernel(useGpuOrder: true); + var accelerator = lease.Accelerator; + var stream = accelerator.CreateStream(); + action(accelerator, stream); + stream.Dispose(); + lease.Dispose(); - /// - /// Runs a GPU action with an acquired accelerator and stream. - /// - /// Action to run with (Accelerator, Stream). - public static void Run(Action action) - { - var lease = GetKernel(useGpuOrder: true); - var accelerator = lease.Accelerator; - var stream = accelerator.CreateStream(); - action(accelerator, stream); - stream.Dispose(); - lease.Dispose(); - - } + } } diff --git a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs index 48f3abb..cd458f1 100644 --- a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs +++ b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs @@ -8,16 +8,10 @@ public static class GpuPrimeWorkLimiter private static SemaphoreSlim _semaphore = new(1, int.MaxValue); private static int _currentLimit = 1; - public static Lease Acquire() - { - // TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve - // GPU jobs stop allocating Lease instances on every acquisition. - var sem = _semaphore; - sem.Wait(); - return new Lease(sem); - } + public static void Acquire() => _semaphore.Wait(); + public static void Release() => _semaphore.Release(); - public static void SetLimit(int value) + public static void SetLimit(int value) { if (value <= 0) { @@ -36,19 +30,4 @@ public static class GpuPrimeWorkLimiter _currentLimit = value; previous.Dispose(); } - - public sealed class Lease : IDisposable - { - private readonly SemaphoreSlim _sem; - - internal Lease(SemaphoreSlim sem) - { - _sem = sem; - } - - public void Dispose() - { - _sem.Release(); - } - } } diff --git a/PerfectNumbers.Core/Gpu/GpuStaticTableInitializer.cs b/PerfectNumbers.Core/Gpu/GpuStaticTableInitializer.cs index 247ee09..d24c9ce 100644 --- a/PerfectNumbers.Core/Gpu/GpuStaticTableInitializer.cs +++ b/PerfectNumbers.Core/Gpu/GpuStaticTableInitializer.cs @@ -11,18 +11,13 @@ internal static class GpuStaticTableInitializer { private static readonly ConcurrentDictionary Initialized = new(AcceleratorReferenceComparer.Instance); - internal static void EnsureStaticTables(Accelerator accelerator) + internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator) { - if (accelerator is null) + Initialized.GetOrAdd(accelerator, acc => { - return; - } - - Initialized.GetOrAdd(accelerator, static acc => - { - PrimeTester.PrimeTesterGpuContextPool.EnsureStaticTables(acc); - GpuKernelPool.PreloadStaticTables(acc); - PrimeOrderGpuHeuristics.PreloadStaticTables(acc); + PrimeTester.PrimeTesterGpuContextPool.EnsureStaticTables(kernels, acc); + GpuKernelPool.PreloadStaticTables(kernels, acc); + PrimeOrderGpuHeuristics.PreloadStaticTables( acc); return 0; }); } diff --git a/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs index 858e2a9..3b71ec2 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs @@ -13,7 +13,7 @@ internal static partial class PrimeOrderGpuHeuristics int factorCount, MontgomeryDivisorData divisor, ArrayView1D candidates, - ArrayView1D resultOut) + ArrayView1D resultOut) { if (index != 0) { diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs index bdfe409..a9467dc 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs @@ -31,11 +31,11 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u ulong step3 = ((exponent % 3UL) << 1) % 3UL; ulong step5 = ((exponent % 5UL) << 1) % 5UL; GpuUInt128 twoPGpu = (GpuUInt128)twoP; - var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); + var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator); ResiduePrimeViews primeViews = default; if (_kernelType == GpuKernelType.Pow2Mod) { - primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); + primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(gpuLease.Kernels, accelerator); } var orderBuffer = accelerator.Allocate1D(batchSize); @@ -121,7 +121,7 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u } ArrayPool.Shared.Return(orderArray); - orderBuffer.Dispose(); + orderBuffer.Dispose(); gpuLease.Dispose(); } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs index 228e13b..f45368f 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs @@ -54,13 +54,13 @@ public class MersenneNumberLucasLehmerGpuTester return false; } - var limiter = GpuPrimeWorkLimiter.Acquire(); + GpuPrimeWorkLimiter.Acquire(); bool result; if (exponent >= 128UL) { if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot)) { - limiter.Dispose(); + GpuPrimeWorkLimiter.Release(); throw new NotSupportedException("NTT parameters for the given exponent are not available."); } @@ -80,7 +80,7 @@ public class MersenneNumberLucasLehmerGpuTester gpu.Dispose(); } - limiter.Dispose(); + GpuPrimeWorkLimiter.Release(); return result; } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs index 3a0d556..8c72652 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs @@ -4,62 +4,63 @@ namespace PerfectNumbers.Core.Gpu; public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuOrder) { - private readonly GpuKernelType _kernelType = kernelType; - private readonly bool _useGpuOrder = useGpuOrder; + private readonly GpuKernelType _kernelType = kernelType; + private readonly bool _useGpuOrder = useGpuOrder; - public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) - { - var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); - var accelerator = gpuLease.Accelerator; - var stream = gpuLease.Stream; - int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR - UInt128 kStart = UInt128.One; - 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 + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) + { + var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); + var accelerator = gpuLease.Accelerator; + var stream = gpuLease.Stream; + int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR + UInt128 kStart = UInt128.One; + 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 - var kernel = _kernelType switch - { - GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel, - _ => gpuLease.IncrementalOrderKernel, - }; + var kernel = _kernelType switch + { + GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel, + _ => gpuLease.IncrementalOrderKernel, + }; - var foundBuffer = accelerator.Allocate1D(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run. - exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); + var foundBuffer = accelerator.Allocate1D(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run. + exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); - UInt128 remaining; - int currentSize; - int found = 0; - while (kStart <= maxK && Volatile.Read(ref isPrime)) - { - remaining = maxK - kStart + 1UL; - currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; - // This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result. - foundBuffer.MemSetToZero(); - // Precompute residue automaton bases for this batch - UInt128 q0 = twoP * kStart + 1UL; - // TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in - // MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the - // single snapshot block instead of recalculating mod 10/8/5/3 in every iteration. - q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); - var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); + UInt128 remaining; + int currentSize; + int found = 0; - // Ensure device has small cycles table for early rejections in order kernels - var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); - kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul, - kernelArgs, foundBuffer.View, smallCyclesView); + var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator); + while (kStart <= maxK && Volatile.Read(ref isPrime)) + { + remaining = maxK - kStart + 1UL; + currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; + // This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result. + foundBuffer.MemSetToZero(); + // Precompute residue automaton bases for this batch + UInt128 q0 = twoP * kStart + 1UL; + // TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in + // MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the + // single snapshot block instead of recalculating mod 10/8/5/3 in every iteration. + q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); + var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); - stream.Synchronize(); - foundBuffer.View.CopyToCPU(ref found, 1); - if (found != 0) - { - Volatile.Write(ref isPrime, false); - break; - } + // Ensure device has small cycles table for early rejections in order kernels + kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul, + kernelArgs, foundBuffer.View, smallCyclesView); - kStart += (UInt128)currentSize; - } + stream.Synchronize(); + foundBuffer.View.CopyToCPU(ref found, 1); + if (found != 0) + { + Volatile.Write(ref isPrime, false); + break; + } - foundBuffer.Dispose(); - gpuLease.Dispose(); - } + kStart += (UInt128)currentSize; + } + + foundBuffer.Dispose(); + gpuLease.Dispose(); + } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index 8a97c87..fdd1e84 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -26,7 +26,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { - var limiter = GpuPrimeWorkLimiter.Acquire(); + GpuPrimeWorkLimiter.Acquire(); var gpuLease = RentAccelerator(); var accelerator = gpuLease.Accelerator; @@ -34,15 +34,16 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) var stream = accelerator.CreateStream(); var kernel = GetPow2ModKernel(accelerator); - var resources = RentResources(accelerator, GpuConstants.ScanBatchSize); + var resources = RentResources(accelerator, GpuConstants.ScanBatchSize); + var gpuKernels = GpuKernelPool.GetKernels(); var orderBuffer = resources.OrderBuffer; ulong[] orderArray = resources.OrderArray; int batchSize = resources.Capacity; // Ensure device has small cycles and primes tables for in-kernel lookup - var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); - ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); + var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuKernels, accelerator); + ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(gpuKernels, accelerator); UInt128 kStart = 1UL; UInt128 limit = maxK + UInt128.One; @@ -108,9 +109,10 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) ReturnResources(accelerator, resources); stream.Dispose(); - // Monitor.Exit(gpuLease.ExecutionLock); - ReturnAccelerator(gpuLease); - limiter.Dispose(); + // Monitor.Exit(gpuLease.ExecutionLock); + gpuKernels.Dispose(); + ReturnAccelerator(gpuLease); + GpuPrimeWorkLimiter.Release(); } private Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> GetPow2ModKernel(Accelerator accelerator) => diff --git a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs index 0413fd9..4389732 100644 --- a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs @@ -1,5 +1,4 @@ using Open.Numeric.Primes; -using System; using ILGPU.Runtime; using System.Runtime.CompilerServices; using PerfectNumbers.Core.Gpu; @@ -42,20 +41,20 @@ public sealed class HeuristicCombinedPrimeTester } - public static void EnsureInitialized() - { - // Intentionally left blank. Accessing this method forces the static constructor to run. - } - - internal static ReadOnlySpan CombinedDivisorsEnding1Span => CombinedDivisorsEnding1; - internal static ReadOnlySpan CombinedDivisorsEnding3Span => CombinedDivisorsEnding3; - internal static ReadOnlySpan CombinedDivisorsEnding7Span => CombinedDivisorsEnding7; - internal static ReadOnlySpan CombinedDivisorsEnding9Span => CombinedDivisorsEnding9; - - internal static ReadOnlySpan CombinedDivisorsEnding1SquaresSpan => CombinedDivisorsEnding1Squares; - internal static ReadOnlySpan CombinedDivisorsEnding3SquaresSpan => CombinedDivisorsEnding3Squares; - internal static ReadOnlySpan CombinedDivisorsEnding7SquaresSpan => CombinedDivisorsEnding7Squares; - internal static ReadOnlySpan CombinedDivisorsEnding9SquaresSpan => CombinedDivisorsEnding9Squares; + public static void EnsureInitialized() + { + // Intentionally left blank. Accessing this method forces the static constructor to run. + } + + internal static ReadOnlySpan CombinedDivisorsEnding1Span => CombinedDivisorsEnding1; + internal static ReadOnlySpan CombinedDivisorsEnding3Span => CombinedDivisorsEnding3; + internal static ReadOnlySpan CombinedDivisorsEnding7Span => CombinedDivisorsEnding7; + internal static ReadOnlySpan CombinedDivisorsEnding9Span => CombinedDivisorsEnding9; + + internal static ReadOnlySpan CombinedDivisorsEnding1SquaresSpan => CombinedDivisorsEnding1Squares; + internal static ReadOnlySpan CombinedDivisorsEnding3SquaresSpan => CombinedDivisorsEnding3Squares; + internal static ReadOnlySpan CombinedDivisorsEnding7SquaresSpan => CombinedDivisorsEnding7Squares; + internal static ReadOnlySpan CombinedDivisorsEnding9SquaresSpan => CombinedDivisorsEnding9Squares; private const ulong Wheel210 = 210UL; @@ -223,39 +222,39 @@ public sealed class HeuristicCombinedPrimeTester }; - private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) - { - var limiter = GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1); - var accelerator = gpu.Accelerator; - var kernel = gpu.HeuristicTrialDivisionKernel; - var flagView1D = gpu.HeuristicFlag.View; - var flagView = flagView1D.AsContiguous(); + private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) + { + GpuPrimeWorkLimiter.Acquire(); + var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1); + var accelerator = gpu.Accelerator; + var kernel = PrimeTester.PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.HeuristicTrialDivisionKernel; + var flagView1D = gpu.HeuristicFlag.View; + var flagView = flagView1D.AsContiguous(); - bool compositeDetected = false; - int divisorLength = GetCombinedDivisors(nMod10).Length; + bool compositeDetected = false; + int divisorLength = GetCombinedDivisors(nMod10).Length; - int compositeFlag = 0; - flagView1D.CopyFromCPU(ref compositeFlag, 1); - kernel( - divisorLength, - flagView, - n, - maxDivisorSquare, - HeuristicGpuDivisorTableKind.Combined, - gpu.HeuristicGpuTables); - accelerator.Synchronize(); - flagView1D.CopyToCPU(ref compositeFlag, 1); - compositeDetected = compositeFlag != 0; + int compositeFlag = 0; + flagView1D.CopyFromCPU(ref compositeFlag, 1); + kernel( + divisorLength, + flagView, + n, + maxDivisorSquare, + HeuristicGpuDivisorTableKind.Combined, + gpu.HeuristicGpuTables); + accelerator.Synchronize(); + flagView1D.CopyToCPU(ref compositeFlag, 1); + compositeDetected = compositeFlag != 0; - gpu.Dispose(); - limiter.Dispose(); - return compositeDetected; - } + gpu.Dispose(); + GpuPrimeWorkLimiter.Release(); + return compositeDetected; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool EvaluateWithOpenNumericFallback(ulong n) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool EvaluateWithOpenNumericFallback(ulong n) { return Prime.Numbers.IsPrime(n); } diff --git a/PerfectNumbers.Core/HeuristicPrimeTester.cs b/PerfectNumbers.Core/HeuristicPrimeTester.cs index a60227b..67c97b1 100644 --- a/PerfectNumbers.Core/HeuristicPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicPrimeTester.cs @@ -213,7 +213,7 @@ public sealed class HeuristicPrimeTester int processed = 0; while (processed < interleaveBatchSize) { - if (!TrySelectNextGroupBDivisor(endingOrder, indices, maxDivisorSquare, out ulong divisor)) + if (!TrySelectNextGroupBDivisor(endingOrder, indices, maxDivisorSquare, out ulong divisor)) { groupBHasMore = false; break; @@ -238,77 +238,77 @@ public sealed class HeuristicPrimeTester } - private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) - { - var limiter = GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1); - var accelerator = gpu.Accelerator; - var kernel = gpu.HeuristicTrialDivisionKernel; - var flagView1D = gpu.HeuristicFlag.View; - var flagView = flagView1D.AsContiguous(); + private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) + { + GpuPrimeWorkLimiter.Acquire(); + var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1); + var accelerator = gpu.Accelerator; + var kernel = PrimeTester.PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.HeuristicTrialDivisionKernel; + var flagView1D = gpu.HeuristicFlag.View; + var flagView = flagView1D.AsContiguous(); - bool compositeDetected = false; - int compositeFlag = 0; + bool compositeDetected = false; + int compositeFlag = 0; - int groupALength = HeuristicPrimeSieves.GroupADivisors.Length; + int groupALength = HeuristicPrimeSieves.GroupADivisors.Length; - flagView1D.CopyFromCPU(ref compositeFlag, 1); - kernel( - groupALength, - flagView, - n, - maxDivisorSquare, - HeuristicGpuDivisorTableKind.GroupA, - gpu.HeuristicGpuTables); - accelerator.Synchronize(); - flagView1D.CopyToCPU(ref compositeFlag, 1); - compositeDetected = compositeFlag != 0; + flagView1D.CopyFromCPU(ref compositeFlag, 1); + kernel( + groupALength, + flagView, + n, + maxDivisorSquare, + HeuristicGpuDivisorTableKind.GroupA, + gpu.HeuristicGpuTables); + accelerator.Synchronize(); + flagView1D.CopyToCPU(ref compositeFlag, 1); + compositeDetected = compositeFlag != 0; - if (!compositeDetected) - { - ReadOnlySpan endingOrder = GetGroupBEndingOrder(nMod10); - for (int i = 0; i < endingOrder.Length && !compositeDetected; i++) - { - byte ending = endingOrder[i]; - HeuristicGpuDivisorTableKind tableKind = ending switch - { - 1 => HeuristicGpuDivisorTableKind.GroupBEnding1, - 7 => HeuristicGpuDivisorTableKind.GroupBEnding7, - 9 => HeuristicGpuDivisorTableKind.GroupBEnding9, - _ => HeuristicGpuDivisorTableKind.GroupA, - }; + if (!compositeDetected) + { + ReadOnlySpan endingOrder = GetGroupBEndingOrder(nMod10); + for (int i = 0; i < endingOrder.Length && !compositeDetected; i++) + { + byte ending = endingOrder[i]; + HeuristicGpuDivisorTableKind tableKind = ending switch + { + 1 => HeuristicGpuDivisorTableKind.GroupBEnding1, + 7 => HeuristicGpuDivisorTableKind.GroupBEnding7, + 9 => HeuristicGpuDivisorTableKind.GroupBEnding9, + _ => HeuristicGpuDivisorTableKind.GroupA, + }; - if (tableKind == HeuristicGpuDivisorTableKind.GroupA) - { - continue; - } + if (tableKind == HeuristicGpuDivisorTableKind.GroupA) + { + continue; + } - int divisorLength = GetGroupBDivisors(ending).Length; + int divisorLength = GetGroupBDivisors(ending).Length; - compositeFlag = 0; - flagView1D.CopyFromCPU(ref compositeFlag, 1); - kernel( - divisorLength, - flagView, - n, - maxDivisorSquare, - tableKind, - gpu.HeuristicGpuTables); - accelerator.Synchronize(); - flagView1D.CopyToCPU(ref compositeFlag, 1); - compositeDetected = compositeFlag != 0; - } - } + compositeFlag = 0; + flagView1D.CopyFromCPU(ref compositeFlag, 1); + kernel( + divisorLength, + flagView, + n, + maxDivisorSquare, + tableKind, + gpu.HeuristicGpuTables); + accelerator.Synchronize(); + flagView1D.CopyToCPU(ref compositeFlag, 1); + compositeDetected = compositeFlag != 0; + } + } - gpu.Dispose(); - limiter.Dispose(); - return compositeDetected; - } + gpu.Dispose(); + GpuPrimeWorkLimiter.Release(); + return compositeDetected; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool EvaluateWithOpenNumericFallback(ulong n) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool EvaluateWithOpenNumericFallback(ulong n) { return Prime.Numbers.IsPrime(n); } @@ -327,7 +327,7 @@ public sealed class HeuristicPrimeTester }; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TrySelectNextGroupBDivisor(ReadOnlySpan endingOrder, Span indices, ulong maxDivisorSquare, out ulong divisor) + private static bool TrySelectNextGroupBDivisor(ReadOnlySpan endingOrder, Span indices, ulong maxDivisorSquare, out ulong divisor) { ulong bestCandidate = ulong.MaxValue; int bestEndingIndex = -1; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 49eb888..07ce6ef 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using ILGPU; using PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core; @@ -106,6 +107,7 @@ internal static partial class PrimeOrderCalculator return candidateOrder; } + // private static ulong specialMaxHits; private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) { int length = factors.Count; @@ -125,6 +127,8 @@ internal static partial class PrimeOrderCalculator return EvaluateSpecialMaxCandidates(stackBuffer, factorSpan, phi, prime, divisorData); } + // Atomic.Add(ref specialMaxHits, 1); + // Console.WriteLine($"Special max GPU hit {specialMaxHits} ({length})"); return EvaluateSpecialMaxCandidatesGpu(stackBuffer, factorSpan, phi, prime, divisorData); } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs index 6dfd404..126603d 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs @@ -25,8 +25,8 @@ internal static partial class PrimeOrderCalculator Accelerator accelerator = lease.Accelerator; AcceleratorStream stream = lease.Stream; - SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(accelerator); - SmallPrimeFactorScratch scratch = GpuKernelPool.EnsureSmallPrimeFactorScratch(accelerator, GpuSmallPrimeFactorSlots); + SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(lease.Kernels, accelerator); + SmallPrimeFactorScratch scratch = GpuKernelPool.EnsureSmallPrimeFactorScratch(lease.Kernels, accelerator, GpuSmallPrimeFactorSlots); var kernel = lease.SmallPrimeFactorKernel; kernel( @@ -92,7 +92,7 @@ internal static partial class PrimeOrderCalculator Accelerator accelerator = lease.Accelerator; AcceleratorStream stream = lease.Stream; - SpecialMaxScratch scratch = GpuKernelPool.EnsureSpecialMaxScratch(accelerator, factorCount); + SpecialMaxScratch scratch = GpuKernelPool.EnsureSpecialMaxScratch(lease.Kernels, accelerator, factorCapacity: factorCount); scratch.FactorsView.SubView(0, factorCount).CopyFromCPU(ref MemoryMarshal.GetReference(factorSpan), factorCount); @@ -109,9 +109,8 @@ internal static partial class PrimeOrderCalculator stream.Synchronize(); - byte result = 0; + ushort result = 0; scratch.ResultView.CopyToCPU(ref result, 1); - lease.Dispose(); return result != 0; diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 4682952..6e64b49 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -95,10 +95,10 @@ public sealed class PrimeTester [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPrimeGpu(ulong n) { - var limiter = GpuPrimeWorkLimiter.Acquire(); + GpuPrimeWorkLimiter.Acquire(); var gpu = PrimeTesterGpuContextPool.Rent(1); var accelerator = gpu.Accelerator; - var kernel = gpu.Kernel; + var kernel = PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.Kernel; ulong value = n; byte flag = 0; @@ -127,7 +127,7 @@ public sealed class PrimeTester outputView.CopyToCPU(ref flag, 1); gpu.Dispose(); - limiter.Dispose(); + GpuPrimeWorkLimiter.Release(); return flag != 0; } @@ -160,10 +160,10 @@ public sealed class PrimeTester [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsPrimeBatchGpu(ReadOnlySpan values, Span results) { - var limiter = GpuPrimeWorkLimiter.Acquire(); + GpuPrimeWorkLimiter.Acquire(); var gpu = PrimeTesterGpuContextPool.Rent(GpuBatchSize); var accelerator = gpu.Accelerator; - var kernel = gpu.Kernel; + var kernel = PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.Kernel; int totalLength = values.Length; int batchSize = GpuBatchSize; @@ -205,7 +205,7 @@ public sealed class PrimeTester } gpu.Dispose(); - limiter.Dispose(); + GpuPrimeWorkLimiter.Release(); } internal static class PrimeTesterGpuContextPool @@ -244,13 +244,8 @@ public sealed class PrimeTester } } - internal static void EnsureStaticTables(Accelerator accelerator) + internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator) { - if (accelerator is null) - { - return; - } - var tables = RentSharedTables(accelerator); var heuristicTables = tables.CreateHeuristicDivisorTables(); HeuristicGpuDivisorTables.InitializeShared(in heuristicTables); @@ -271,8 +266,8 @@ public sealed class PrimeTester return; } - Accelerator accelerator = SharedGpuContext.Accelerator; - GpuStaticTableInitializer.EnsureStaticTables(accelerator); + KernelContainer kernels = GpuKernelPool.GetKernels(); + GpuStaticTableInitializer.EnsureStaticTables(kernels, SharedGpuContext.Accelerator); int toCreate = target - WarmedLeaseCount; for (int i = 0; i < toCreate; i++) @@ -280,6 +275,7 @@ public sealed class PrimeTester Pool.Enqueue(new PrimeTesterGpuContextLease(minBufferCapacity)); } + kernels.Dispose(); WarmedLeaseCount = target; } } @@ -383,8 +379,11 @@ public sealed class PrimeTester internal readonly Context AcceleratorContext; public readonly Accelerator Accelerator; - public readonly Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> Kernel; - public readonly Action, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables> HeuristicTrialDivisionKernel; + + public static readonly Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> Kernel = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); + + public static readonly Action, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables> HeuristicTrialDivisionKernel = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel); + public MemoryBuffer1D Input = null!; public MemoryBuffer1D Output = null!; public MemoryBuffer1D HeuristicFlag = null!; @@ -423,9 +422,9 @@ public sealed class PrimeTester { AcceleratorContext = SharedGpuContext.Context; Accelerator = SharedGpuContext.Accelerator; - GpuStaticTableInitializer.EnsureStaticTables(Accelerator); - Kernel = Accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); - HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel); + // GpuStaticTableInitializer.EnsureStaticTables(Accelerator); + // Kernel = Accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); + // HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel); _sharedTables = RentSharedTables(Accelerator);