diff --git a/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs index db1c2fb..0cf9227 100644 --- a/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Threading; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using Open.Numeric.Primes; @@ -12,7 +9,6 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class PrimeTesterBenchmarks { - private static readonly HeuristicCombinedPrimeTester _heuristicCombinedTester = new(); private static readonly HeuristicPrimeTester _heuristicTester = new(); private ulong[] _candidates = []; @@ -34,7 +30,7 @@ public class PrimeTesterBenchmarks _ = Prime.Numbers.IsPrime(_candidates[0]); } - [Benchmark(Baseline = true)] + [Benchmark] public int HeuristicCpu() { HeuristicPrimeTester tester = _heuristicTester; @@ -104,18 +100,15 @@ public class PrimeTesterBenchmarks return primeCount; } - private static readonly PrimeTester _nonHeuristicTester = new(); - [Benchmark] public int NonHeuristicGpu() { - PrimeTester tester = _nonHeuristicTester; ulong[] values = _candidates; int primeCount = 0; for (int i = 0; i < values.Length; i++) { - if (tester.IsPrimeGpu(values[i])) + if (PrimeTester.IsPrimeGpu(values[i])) { primeCount++; } @@ -124,7 +117,7 @@ public class PrimeTesterBenchmarks return primeCount; } - [Benchmark] + [Benchmark(Baseline = true)] public int NonHeuristicCpu() { ulong[] values = _candidates; diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index bcd7937..48e8e27 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -12,7 +12,7 @@ namespace EvenPerfectBitScanner; internal static class Program { - private static ThreadLocal PrimeTesters = null!; + // private static ThreadLocal PrimeTesters = null!; private static ThreadLocal MersenneTesters = null!; private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP; private static MersenneNumberDivisorGpuTester? _divisorTester; @@ -176,7 +176,7 @@ internal static class Program blockSize = useByDivisor ? 1 : blockSize; CandidatesCalculator.Initialize(currentP); - PrimeTesters = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); + // PrimeTesters = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); // Note: --primes-device controls default device for library kernels; p primality remains CPU here. // Initialize per-thread p residue tracker (Identity model) at currentP if (!useDivisor && !_cliArguments.UseByDivisor) @@ -626,7 +626,7 @@ internal static class Program { if (!(_runPrimesOnCpu ? PrimeTester.IsPrime(p) - : PrimeTesters.Value!.IsPrimeGpu(p))) + : PrimeTester.IsPrimeGpu(p))) { _lastCompositeP = true; return false; 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.Tests/Gpu/PrimeTesterGpuTests.cs b/PerfectNumbers.Core.Tests/Gpu/PrimeTesterGpuTests.cs index 9ba37b0..8f6ed46 100644 --- a/PerfectNumbers.Core.Tests/Gpu/PrimeTesterGpuTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/PrimeTesterGpuTests.cs @@ -16,7 +16,7 @@ public class PrimeTesterGpuTests ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL]; foreach (ulong prime in primes) { - tester.IsPrimeGpu(prime).Should().BeTrue(); + PrimeTester.IsPrimeGpu(prime).Should().BeTrue(); } } finally @@ -34,7 +34,7 @@ public class PrimeTesterGpuTests ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL]; foreach (ulong composite in composites) { - tester.IsPrimeGpu(composite).Should().BeFalse(); + PrimeTester.IsPrimeGpu(composite).Should().BeFalse(); } } finally diff --git a/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs b/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs index d1efe98..72dbcfb 100644 --- a/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs +++ b/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs @@ -39,7 +39,7 @@ public class PrimeTesterIsPrimeTests { var tester = new PrimeTester(); - tester.IsPrimeGpu(n).Should().Be(expected); + PrimeTester.IsPrimeGpu(n).Should().Be(expected); } [Fact] @@ -64,7 +64,7 @@ public class PrimeTesterIsPrimeTests var testers = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); Parallel.For(0, primes.Length, i => { - results[i] = testers.Value!.IsPrimeGpu(primes[i]); + results[i] = PrimeTester.IsPrimeGpu(primes[i]); }); results.Should().AllBeEquivalentTo(true); @@ -73,7 +73,7 @@ public class PrimeTesterIsPrimeTests { foreach (ulong prime in primes) { - testers.Value!.IsPrimeGpu(prime).Should().BeTrue(); + PrimeTester.IsPrimeGpu(prime).Should().BeTrue(); } }); } diff --git a/PerfectNumbers.Core/Gpu/GpuContextPool.cs b/PerfectNumbers.Core/Gpu/GpuContextPool.cs index b6218c4..ae11bda 100644 --- a/PerfectNumbers.Core/Gpu/GpuContextPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuContextPool.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; @@ -15,14 +14,12 @@ public static class GpuContextPool public readonly Context Context; public readonly Accelerator Accelerator; - private static readonly Context _sharedContext = Context.CreateDefault(); - private static readonly Device _sharedDevice = _sharedContext.GetPreferredDevice(false); - private static readonly Accelerator _sharedAccelerator = _sharedDevice.CreateAccelerator(_sharedContext); - - public PooledContext() + public PooledContext(KernelContainer kernels) { - Context = _sharedContext; - Accelerator = _sharedAccelerator; + Context = SharedGpuContext.Context; + Accelerator = SharedGpuContext.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 @@ -48,7 +45,7 @@ public static class GpuContextPool private static int WarmedGpuContextCount; // Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu. - public static GpuContextLease Rent() + public static GpuContextLease Rent() { if (GpuPool.TryDequeue(out var gpu)) { @@ -56,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())); } @@ -74,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 a79af7a..1db24b1 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; +using PerfectNumbers.Core; using static PerfectNumbers.Core.Gpu.GpuContextPool; namespace PerfectNumbers.Core.Gpu; @@ -10,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; } @@ -56,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); @@ -70,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); @@ -84,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); @@ -98,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); @@ -112,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); @@ -121,12 +121,26 @@ public sealed class GpuKernelLease } } + public Action, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D> SpecialMaxKernel + { + get + { + var accel = Accelerator; + return KernelContainer.InitOnce(ref Kernels!.SpecialMax, () => + { + var loaded = accel.LoadAutoGroupedStreamKernel, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel); + var kernel = KernelUtil.GetKernel(loaded); + return kernel.CreateLauncherDelegate, int, MontgomeryDivisorData, ArrayView1D, ArrayView1D>>(); + }); + } + } + public Action, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> SmallPrimeFactorKernel { 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); @@ -143,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 0257fc9..365b4d6 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -1,3 +1,4 @@ +using PerfectNumbers.Core; using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; @@ -7,297 +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; + // 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? 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( + MemoryBuffer1D factorValues, + MemoryBuffer1D candidateValues, + MemoryBuffer1D resultSlot) +{ + public readonly MemoryBuffer1D FactorValues = factorValues; + public readonly MemoryBuffer1D CandidateValues = candidateValues; + public readonly MemoryBuffer1D ResultSlot = resultSlot; + + 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(); - // 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; - } + public static void Return(KernelContainer container) => KernelCache.Enqueue(container); - // 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; - } + internal static void PreloadStaticTables(KernelContainer kernels, Accelerator accelerator) + { + EnsureSmallCyclesOnDevice(kernels, accelerator); + EnsureSmallPrimesOnDevice(kernels, accelerator); + EnsureSmallPrimeFactorTables(kernels, accelerator); + } - 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; - } - } + // 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; + } - 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); - } + 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; + } - 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); - } + 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); + } - var hostLastOne = PrimesGenerator.SmallPrimesLastOne; - var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven; - var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; - var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; + 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 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 hostLastOne = PrimesGenerator.SmallPrimesLastOne; + var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven; + var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; + var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; - kernels.SmallPrimesLastOne = deviceLastOne; - kernels.SmallPrimesLastSeven = deviceLastSeven; - kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; - kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; + 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); - return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); - } - } + kernels.SmallPrimesLastOne = deviceLastOne; + kernels.SmallPrimesLastSeven = deviceLastSeven; + kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; + kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; - 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); - } + return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); + } - lock (kernels) - { - if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares) - { - return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.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); + } - var hostPrimes = PrimesGenerator.SmallPrimes; - var hostSquares = PrimesGenerator.SmallPrimesPow2; + if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares) + { + return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length); + } - var devicePrimes = accelerator.Allocate1D(hostPrimes.Length); - devicePrimes.View.CopyFromCPU(hostPrimes); - var deviceSquares = accelerator.Allocate1D(hostSquares.Length); - deviceSquares.View.CopyFromCPU(hostSquares); + var hostPrimes = PrimesGenerator.SmallPrimes; + var hostSquares = PrimesGenerator.SmallPrimesPow2; - kernels.SmallPrimeFactorsPrimes = devicePrimes; - kernels.SmallPrimeFactorsSquares = deviceSquares; + var devicePrimes = accelerator.Allocate1D(hostPrimes.Length); + devicePrimes.View.CopyFromCPU(hostPrimes); + var deviceSquares = accelerator.Allocate1D(hostSquares.Length); + deviceSquares.View.CopyFromCPU(hostSquares); - return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); - } - } + kernels.SmallPrimeFactorsPrimes = devicePrimes; + kernels.SmallPrimeFactorsSquares = deviceSquares; - 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; - } + return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); + } - MemoryBuffer1D? exponentSlots = kernels.SmallPrimeFactorExponentSlots; - if (exponentSlots is null || exponentSlots.Length < slotCount) - { - exponentSlots?.Dispose(); - exponentSlots = accelerator.Allocate1D(slotCount); - kernels.SmallPrimeFactorExponentSlots = exponentSlots; - } + 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? countSlot = kernels.SmallPrimeFactorCountSlot; - if (countSlot is null) - { - countSlot = accelerator.Allocate1D(1); - kernels.SmallPrimeFactorCountSlot = countSlot; - } + MemoryBuffer1D? exponentSlots = kernels.SmallPrimeFactorExponentSlots; + if (exponentSlots is null || exponentSlots.Length < slotCount) + { + exponentSlots?.Dispose(); + exponentSlots = accelerator.Allocate1D(slotCount); + kernels.SmallPrimeFactorExponentSlots = exponentSlots; + } - MemoryBuffer1D? remainingSlot = kernels.SmallPrimeFactorRemainingSlot; - if (remainingSlot is null) - { - remainingSlot = accelerator.Allocate1D(1); - kernels.SmallPrimeFactorRemainingSlot = remainingSlot; - } + MemoryBuffer1D? countSlot = kernels.SmallPrimeFactorCountSlot; + if (countSlot is null) + { + countSlot = accelerator.Allocate1D(1); + kernels.SmallPrimeFactorCountSlot = countSlot; + } - return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!); - } - } + MemoryBuffer1D? remainingSlot = kernels.SmallPrimeFactorRemainingSlot; + if (remainingSlot is null) + { + remainingSlot = accelerator.Allocate1D(1); + kernels.SmallPrimeFactorRemainingSlot = remainingSlot; + } - 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); - } + return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!); + } - /// - /// 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(); + 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; + } - } + 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; + } + + 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); + } + + /// + /// 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 new file mode 100644 index 0000000..d24c9ce --- /dev/null +++ b/PerfectNumbers.Core/Gpu/GpuStaticTableInitializer.cs @@ -0,0 +1,39 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using ILGPU; +using ILGPU.Runtime; +using PerfectNumbers.Core; + +namespace PerfectNumbers.Core.Gpu; + +internal static class GpuStaticTableInitializer +{ + private static readonly ConcurrentDictionary Initialized = new(AcceleratorReferenceComparer.Instance); + + internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator) + { + Initialized.GetOrAdd(accelerator, acc => + { + PrimeTester.PrimeTesterGpuContextPool.EnsureStaticTables(kernels, acc); + GpuKernelPool.PreloadStaticTables(kernels, acc); + PrimeOrderGpuHeuristics.PreloadStaticTables( acc); + return 0; + }); + } + + private sealed class AcceleratorReferenceComparer : IEqualityComparer + { + internal static AcceleratorReferenceComparer Instance { get; } = new(); + + public bool Equals(Accelerator? x, Accelerator? y) + { + return ReferenceEquals(x, y); + } + + public int GetHashCode(Accelerator obj) + { + return RuntimeHelpers.GetHashCode(obj); + } + } +} diff --git a/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs new file mode 100644 index 0000000..3b71ec2 --- /dev/null +++ b/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs @@ -0,0 +1,80 @@ +using ILGPU; +using ILGPU.Runtime; +using PerfectNumbers.Core; + +namespace PerfectNumbers.Core.Gpu; + +internal static partial class PrimeOrderGpuHeuristics +{ + internal static void EvaluateSpecialMaxCandidatesKernel( + Index1D index, + ulong phi, + ArrayView1D factors, + int factorCount, + MontgomeryDivisorData divisor, + ArrayView1D candidates, + ArrayView1D resultOut) + { + if (index != 0) + { + return; + } + + int actual = 0; + for (int i = 0; i < factorCount; i++) + { + ulong factor = factors[i]; + if (factor <= 1UL) + { + continue; + } + + ulong reduced = phi / factor; + if (reduced == 0UL) + { + continue; + } + + candidates[actual] = reduced; + actual++; + } + + if (actual == 0) + { + resultOut[0] = 1; + return; + } + + for (int i = 1; i < actual; i++) + { + ulong current = candidates[i]; + int insert = i - 1; + while (insert >= 0 && candidates[insert] > current) + { + candidates[insert + 1] = candidates[insert]; + insert--; + } + + candidates[insert + 1] = current; + } + + var divisorPartial = new GpuDivisorPartialData(divisor.Modulus); + var stepper = new ExponentRemainderStepperGpu(divisorPartial); + if (stepper.InitializeIsUnityGpu(candidates[0])) + { + resultOut[0] = 0; + return; + } + + for (int i = 1; i < actual; i++) + { + if (stepper.ComputeNextIsUnityGpu(candidates[i])) + { + resultOut[0] = 0; + return; + } + } + + resultOut[0] = 1; + } +} 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/Gpu/PrimeOrderGpuHeuristics.cs b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs index bfb9851..8664d8b 100644 --- a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs +++ b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs @@ -18,6 +18,11 @@ internal enum GpuPow2ModStatus internal static partial class PrimeOrderGpuHeuristics { + internal static void PreloadStaticTables(Accelerator accelerator) + { + _ = GetSmallPrimeDeviceCache(accelerator); + } + private static readonly ConcurrentDictionary OverflowedPrimes = new(); private static readonly ConcurrentDictionary OverflowedPrimesWide = new(); private static readonly ConcurrentDictionary, MontgomeryDivisorData, ArrayView1D>> Pow2ModKernelCache = new(); diff --git a/PerfectNumbers.Core/Gpu/SharedGpuContext.cs b/PerfectNumbers.Core/Gpu/SharedGpuContext.cs new file mode 100644 index 0000000..ec5c4cb --- /dev/null +++ b/PerfectNumbers.Core/Gpu/SharedGpuContext.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading; +using ILGPU; +using ILGPU.Runtime; + +namespace PerfectNumbers.Core.Gpu; + +internal static class SharedGpuContext +{ + private static readonly Lazy s_context = new(() => ILGPU.Context.CreateDefault(), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy s_device = new(() => s_context.Value.GetPreferredDevice(false), LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy s_accelerator = new(() => s_device.Value.CreateAccelerator(s_context.Value), LazyThreadSafetyMode.ExecutionAndPublication); + + internal static Context Context => s_context.Value; + + internal static Device Device => s_device.Value; + + internal static Accelerator Accelerator => s_accelerator.Value; +} 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/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 149bc77..9e7b026 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -412,7 +412,7 @@ public class MersenneDivisorCycles // Console.WriteLine($"MersenneDivisorCycles.TryFactorIntoCountsInternal hits {Volatile.Read(ref _tryFactorIntoCountsInternalHits)}"); // HeuristicPrimeTester primeTester = _primeTester ??= new(); - if (PrimeTester.IsPrime(remaining)) + if (PrimeTester.IsPrimeGpu(remaining)) // if (PrimeTester.IsPrimeCpu(remaining, CancellationToken.None)) { AddFactor(counts, remaining); @@ -943,7 +943,7 @@ public class MersenneDivisorCycles // HeuristicPrimeTester primeTester = _primeTester ??= new(); // if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrimeCpu(divisor, CancellationToken.None)) // if (!skipPrimeOrderHeuristic && primeTester.IsPrimeGpu(divisor)) - if (!skipPrimeOrderHeuristic && PrimeTester.IsPrime(divisor)) + if (!skipPrimeOrderHeuristic && PrimeTester.IsPrimeGpu(divisor)) { ulong computedOrder = PrimeOrderCalculator.Calculate( divisor, diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 702039e..07ce6ef 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -1,4 +1,3 @@ -using System; using System.Buffers; using System.Diagnostics; using System.Numerics; @@ -108,7 +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; @@ -121,16 +120,16 @@ internal static partial class PrimeOrderCalculator ReadOnlySpan factorSpan = new(factors.Factors, 0, length); - if (length <= 32) + Span stackBuffer = stackalloc ulong[length]; + + if (length <= 7) { - Span stackBuffer = stackalloc ulong[length]; return EvaluateSpecialMaxCandidates(stackBuffer, factorSpan, phi, prime, divisorData); } - ulong[] rented = ThreadStaticPools.UlongPool.Rent(length); - bool result = EvaluateSpecialMaxCandidates(rented.AsSpan(0, length), factorSpan, phi, prime, divisorData); - ThreadStaticPools.UlongPool.Return(rented, clearArray: false); - return result; + // Atomic.Add(ref specialMaxHits, 1); + // Console.WriteLine($"Special max GPU hit {specialMaxHits} ({length})"); + return EvaluateSpecialMaxCandidatesGpu(stackBuffer, factorSpan, phi, prime, divisorData); } private static bool EvaluateSpecialMaxCandidates(Span buffer, ReadOnlySpan factors, ulong phi, ulong prime, in MontgomeryDivisorData divisorData) @@ -1194,7 +1193,7 @@ internal static partial class PrimeOrderCalculator // Console.WriteLine($"Partial factor pending hits {Volatile.Read(ref _partialFactorPendingHits)}"); // bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None); - bool isPrime = PrimeTester.IsPrime(composite); + bool isPrime = PrimeTester.IsPrimeGpu(composite); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); entry = entry.WithPrimality(isPrime); @@ -1231,7 +1230,7 @@ internal static partial class PrimeOrderCalculator // Atomic.Add(ref _partialFactorCofactorHits, 1UL); // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); - cofactorIsPrime = PrimeTester.IsPrime(cofactor); + cofactorIsPrime = PrimeTester.IsPrimeGpu(cofactor); // cofactorIsPrime = PrimeTester.IsPrimeGpu(cofactor); // cofactorIsPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(cofactor); } @@ -1701,7 +1700,7 @@ internal static partial class PrimeOrderCalculator // HeuristicPrimeTester tester = _tester ??= new(); // bool isPrime = primeTester.HeuristicIsPrimeGpu(value); - bool isPrime = PrimeTester.IsPrime(value); + bool isPrime = PrimeTester.IsPrimeGpu(value); // bool isPrime = PrimeTester.IsPrimeGpu(value); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(value); if (!knownComposite && isPrime) @@ -1717,7 +1716,7 @@ internal static partial class PrimeOrderCalculator // Console.WriteLine($"Factor completely after PollardRho hits {Volatile.Read(ref _factorCompletelyPollardRhoHits)}"); // isPrime = primeTester.HeuristicIsPrimeGpu(factor); - isPrime = PrimeTester.IsPrime(factor); + isPrime = PrimeTester.IsPrimeGpu(factor); // isPrime = PrimeTester.IsPrimeGpu(factor); // isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factor); if (isPrime) @@ -1754,7 +1753,7 @@ internal static partial class PrimeOrderCalculator // Atomic.Add(ref _partialFactorCofactorHits, 1UL); // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); - isPrime = PrimeTester.IsPrime(quotient); + isPrime = PrimeTester.IsPrimeGpu(quotient); // isPrime = PrimeTester.IsPrimeGpu(quotient); // isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(quotient); } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs index 41ef94a..126603d 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs @@ -9,63 +9,111 @@ namespace PerfectNumbers.Core; internal static partial class PrimeOrderCalculator { - private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2; + private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2; - private const int GpuSmallPrimeFactorSlots = 64; + private const int GpuSmallPrimeFactorSlots = 64; - private static bool TryPopulateSmallPrimeFactorsGpu(ulong value, uint limit, Dictionary counts, out int factorCount, out ulong remaining) - { - var primeBufferArray = ThreadStaticPools.UlongPool.Rent(GpuSmallPrimeFactorSlots); - var exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots); - Span primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); - Span exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); - remaining = value; + private static bool TryPopulateSmallPrimeFactorsGpu(ulong value, uint limit, Dictionary counts, out int factorCount, out ulong remaining) + { + var primeBufferArray = ThreadStaticPools.UlongPool.Rent(GpuSmallPrimeFactorSlots); + var exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots); + Span primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); + Span exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); + remaining = value; - var lease = GpuKernelPool.GetKernel(useGpuOrder: true); - Accelerator accelerator = lease.Accelerator; - AcceleratorStream stream = lease.Stream; + var lease = GpuKernelPool.GetKernel(useGpuOrder: true); + 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( - stream, - 1, - value, - limit, - tables.PrimesView, - tables.SquaresView, - tables.Count, - scratch.PrimeSlots.View, - scratch.ExponentSlots.View, - scratch.CountSlot.View, - scratch.RemainingSlot.View); + var kernel = lease.SmallPrimeFactorKernel; + kernel( + stream, + 1, + value, + limit, + tables.PrimesView, + tables.SquaresView, + tables.Count, + scratch.PrimeSlots.View, + scratch.ExponentSlots.View, + scratch.CountSlot.View, + scratch.RemainingSlot.View); - stream.Synchronize(); + stream.Synchronize(); - factorCount = 0; - scratch.CountSlot.View.CopyToCPU(ref factorCount, 1); - factorCount = Math.Min(factorCount, GpuSmallPrimeFactorSlots); + factorCount = 0; + scratch.CountSlot.View.CopyToCPU(ref factorCount, 1); + factorCount = Math.Min(factorCount, GpuSmallPrimeFactorSlots); - if (factorCount > 0) - { - scratch.PrimeSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(primeBuffer), factorCount); - scratch.ExponentSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentBuffer), factorCount); - } + if (factorCount > 0) + { + scratch.PrimeSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(primeBuffer), factorCount); + scratch.ExponentSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentBuffer), factorCount); + } - scratch.RemainingSlot.View.CopyToCPU(ref remaining, 1); + scratch.RemainingSlot.View.CopyToCPU(ref remaining, 1); - for (int i = 0; i < factorCount; i++) - { - ulong primeValue = primeBuffer[i]; - int exponent = exponentBuffer[i]; - counts.Add(primeValue, exponent); - } + for (int i = 0; i < factorCount; i++) + { + ulong primeValue = primeBuffer[i]; + int exponent = exponentBuffer[i]; + counts.Add(primeValue, exponent); + } - lease.Dispose(); - ThreadStaticPools.UlongPool.Return(primeBufferArray); - ThreadStaticPools.IntPool.Return(exponentBufferArray); - return true; - } + lease.Dispose(); + ThreadStaticPools.UlongPool.Return(primeBufferArray); + ThreadStaticPools.IntPool.Return(exponentBufferArray); + return true; + } + + private static bool EvaluateSpecialMaxCandidatesGpu( + Span buffer, + ReadOnlySpan factors, + ulong phi, + ulong prime, + in MontgomeryDivisorData divisorData) + { + if (factors.Length == 0) + { + return true; + } + + int factorCount = factors.Length; + Span factorSpan = buffer[..factorCount]; + for (int i = 0; i < factorCount; i++) + { + factorSpan[i] = factors[i].Value; + } + + var lease = GpuKernelPool.GetKernel(useGpuOrder: true); + Accelerator accelerator = lease.Accelerator; + AcceleratorStream stream = lease.Stream; + + SpecialMaxScratch scratch = GpuKernelPool.EnsureSpecialMaxScratch(lease.Kernels, accelerator, factorCapacity: factorCount); + + scratch.FactorsView.SubView(0, factorCount).CopyFromCPU(ref MemoryMarshal.GetReference(factorSpan), factorCount); + + var kernel = lease.SpecialMaxKernel; + kernel( + stream, + 1, + phi, + scratch.FactorsView, + factorCount, + divisorData, + scratch.CandidatesView, + scratch.ResultView); + + stream.Synchronize(); + + 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 75f2b0d..6e64b49 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -93,50 +93,43 @@ public sealed class PrimeTester } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsPrimeGpu(ulong n) + public static bool IsPrimeGpu(ulong n) { - // bool belowGpuRange = n < 31UL; + GpuPrimeWorkLimiter.Acquire(); + var gpu = PrimeTesterGpuContextPool.Rent(1); + var accelerator = gpu.Accelerator; + var kernel = PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.Kernel; - // if (belowGpuRange) - // { - return IsPrime(n); - // } + ulong value = n; + byte flag = 0; - // var limiter = GpuPrimeWorkLimiter.Acquire(); - // var gpu = PrimeTesterGpuContextPool.Rent(1); - // var accelerator = gpu.Accelerator; - // var kernel = gpu.Kernel; + var input = gpu.Input; + var output = gpu.Output; + var inputView = input.View; + var outputView = output.View; - // ulong value = n; - // byte flag = 0; + inputView.CopyFromCPU(ref value, 1); + kernel( + 1, + inputView, + gpu.DevicePrimesDefault.View, + gpu.DevicePrimesLastOne.View, + gpu.DevicePrimesLastSeven.View, + gpu.DevicePrimesLastThree.View, + gpu.DevicePrimesLastNine.View, + gpu.DevicePrimesPow2Default.View, + gpu.DevicePrimesPow2LastOne.View, + gpu.DevicePrimesPow2LastSeven.View, + gpu.DevicePrimesPow2LastThree.View, + gpu.DevicePrimesPow2LastNine.View, + outputView); + accelerator.Synchronize(); + outputView.CopyToCPU(ref flag, 1); - // var input = gpu.Input; - // var output = gpu.Output; - // var inputView = input.View; - // var outputView = output.View; + gpu.Dispose(); + GpuPrimeWorkLimiter.Release(); - // inputView.CopyFromCPU(ref value, 1); - // kernel( - // 1, - // inputView, - // gpu.DevicePrimesDefault.View, - // gpu.DevicePrimesLastOne.View, - // gpu.DevicePrimesLastSeven.View, - // gpu.DevicePrimesLastThree.View, - // gpu.DevicePrimesLastNine.View, - // gpu.DevicePrimesPow2Default.View, - // gpu.DevicePrimesPow2LastOne.View, - // gpu.DevicePrimesPow2LastSeven.View, - // gpu.DevicePrimesPow2LastThree.View, - // gpu.DevicePrimesPow2LastNine.View, - // outputView); - // accelerator.Synchronize(); - // outputView.CopyToCPU(ref flag, 1); - - // gpu.Dispose(); - // limiter.Dispose(); - - // return flag != 0; + return flag != 0; } public static int GpuBatchSize { get; set; } = 262_144; @@ -167,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; @@ -212,7 +205,7 @@ public sealed class PrimeTester } gpu.Dispose(); - limiter.Dispose(); + GpuPrimeWorkLimiter.Release(); } internal static class PrimeTesterGpuContextPool @@ -221,23 +214,16 @@ public sealed class PrimeTester private static readonly object WarmUpLock = new(); private static int WarmedLeaseCount; private static readonly object SharedTablesLock = new(); - private static readonly Dictionary SharedTables = new(); - - private static string CreateAcceleratorKey(Accelerator accelerator) - { - return accelerator.AcceleratorType + ":" + accelerator.Name; - } + private static readonly Dictionary SharedTables = new(AcceleratorReferenceComparer.Instance); private static SharedHeuristicGpuTables RentSharedTables(Accelerator accelerator) { - string key = CreateAcceleratorKey(accelerator); - lock (SharedTablesLock) { - if (!SharedTables.TryGetValue(key, out var tables)) + if (!SharedTables.TryGetValue(accelerator, out var tables)) { - tables = new SharedHeuristicGpuTables(accelerator, key); - SharedTables.Add(key, tables); + tables = new SharedHeuristicGpuTables(accelerator); + SharedTables.Add(accelerator, tables); } tables.AddReference(); @@ -258,6 +244,14 @@ public sealed class PrimeTester } } + internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator) + { + var tables = RentSharedTables(accelerator); + var heuristicTables = tables.CreateHeuristicDivisorTables(); + HeuristicGpuDivisorTables.InitializeShared(in heuristicTables); + ReleaseSharedTables(tables); + } + internal static void WarmUp(int target, int minBufferCapacity) { if (target <= 0) @@ -272,12 +266,16 @@ public sealed class PrimeTester return; } + KernelContainer kernels = GpuKernelPool.GetKernels(); + GpuStaticTableInitializer.EnsureStaticTables(kernels, SharedGpuContext.Accelerator); + int toCreate = target - WarmedLeaseCount; for (int i = 0; i < toCreate; i++) { Pool.Enqueue(new PrimeTesterGpuContextLease(minBufferCapacity)); } + kernels.Dispose(); WarmedLeaseCount = target; } } @@ -319,6 +317,8 @@ public sealed class PrimeTester } SharedTables.Clear(); + var emptyTables = default(HeuristicGpuDivisorTables); + HeuristicGpuDivisorTables.InitializeShared(in emptyTables); } WarmedLeaseCount = 0; @@ -354,6 +354,20 @@ public sealed class PrimeTester WarmedLeaseCount = Pool.Count; } + + lock (SharedTablesLock) + { + if (SharedTables.Remove(accelerator, out var tables)) + { + tables.Dispose(); + } + + if (SharedTables.Count == 0) + { + var emptyTables = default(HeuristicGpuDivisorTables); + HeuristicGpuDivisorTables.InitializeShared(in emptyTables); + } + } } internal sealed class PrimeTesterGpuContextLease : IDisposable @@ -365,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!; @@ -401,36 +418,17 @@ public sealed class PrimeTester public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables; - private static readonly Context _sharedContext = Context.CreateDefault(); - private static readonly Device _sharedDevice = _sharedContext.GetPreferredDevice(false); - private static readonly Accelerator _sharedAccelerator = _sharedDevice.CreateAccelerator(_sharedContext); - internal PrimeTesterGpuContextLease(int minBufferCapacity) { - AcceleratorContext = _sharedContext;// Context.CreateDefault(); - Accelerator = _sharedAccelerator; //AcceleratorContext.GetPreferredDevice(false).CreateAccelerator(AcceleratorContext); - 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); + 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); _sharedTables = RentSharedTables(Accelerator); - _heuristicDivisorTables = new HeuristicGpuDivisorTables( - _sharedTables.HeuristicCombinedDivisorsEnding1.View, - _sharedTables.HeuristicCombinedDivisorsEnding3.View, - _sharedTables.HeuristicCombinedDivisorsEnding7.View, - _sharedTables.HeuristicCombinedDivisorsEnding9.View, - _sharedTables.HeuristicCombinedDivisorSquaresEnding1.View, - _sharedTables.HeuristicCombinedDivisorSquaresEnding3.View, - _sharedTables.HeuristicCombinedDivisorSquaresEnding7.View, - _sharedTables.HeuristicCombinedDivisorSquaresEnding9.View, - _sharedTables.HeuristicGroupADivisors.View, - _sharedTables.HeuristicGroupADivisorSquares.View, - _sharedTables.HeuristicGroupBDivisorsEnding1.View, - _sharedTables.HeuristicGroupBDivisorSquaresEnding1.View, - _sharedTables.HeuristicGroupBDivisorsEnding7.View, - _sharedTables.HeuristicGroupBDivisorSquaresEnding7.View, - _sharedTables.HeuristicGroupBDivisorsEnding9.View, - _sharedTables.HeuristicGroupBDivisorSquaresEnding9.View); + _heuristicDivisorTables = _sharedTables.CreateHeuristicDivisorTables(); HeuristicGpuDivisorTables.InitializeShared(in _heuristicDivisorTables); EnsureCapacity(minBufferCapacity); @@ -481,13 +479,27 @@ public sealed class PrimeTester } } + private sealed class AcceleratorReferenceComparer : IEqualityComparer + { + internal static AcceleratorReferenceComparer Instance { get; } = new(); + + public bool Equals(Accelerator? x, Accelerator? y) + { + return ReferenceEquals(x, y); + } + + public int GetHashCode(Accelerator obj) + { + return RuntimeHelpers.GetHashCode(obj); + } + } + private sealed class SharedHeuristicGpuTables { private int _referenceCount; - internal SharedHeuristicGpuTables(Accelerator accelerator, string key) + internal SharedHeuristicGpuTables(Accelerator accelerator) { - Key = key; var primesDefault = DivisorGenerator.SmallPrimes; DevicePrimesDefault = accelerator.Allocate1D(primesDefault.Length); @@ -568,7 +580,6 @@ public sealed class PrimeTester HeuristicCombinedDivisorSquaresEnding9 = CopySpanToDevice(accelerator, combinedSquaresEnding9); } - internal string Key { get; } internal MemoryBuffer1D DevicePrimesDefault { get; } internal MemoryBuffer1D DevicePrimesLastOne { get; } internal MemoryBuffer1D DevicePrimesLastSeven { get; } @@ -596,6 +607,27 @@ public sealed class PrimeTester internal MemoryBuffer1D HeuristicCombinedDivisorsEnding9 { get; } internal MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding9 { get; } + internal HeuristicGpuDivisorTables CreateHeuristicDivisorTables() + { + return new HeuristicGpuDivisorTables( + HeuristicCombinedDivisorsEnding1.View, + HeuristicCombinedDivisorsEnding3.View, + HeuristicCombinedDivisorsEnding7.View, + HeuristicCombinedDivisorsEnding9.View, + HeuristicCombinedDivisorSquaresEnding1.View, + HeuristicCombinedDivisorSquaresEnding3.View, + HeuristicCombinedDivisorSquaresEnding7.View, + HeuristicCombinedDivisorSquaresEnding9.View, + HeuristicGroupADivisors.View, + HeuristicGroupADivisorSquares.View, + HeuristicGroupBDivisorsEnding1.View, + HeuristicGroupBDivisorSquaresEnding1.View, + HeuristicGroupBDivisorsEnding7.View, + HeuristicGroupBDivisorSquaresEnding7.View, + HeuristicGroupBDivisorsEnding9.View, + HeuristicGroupBDivisorSquaresEnding9.View); + } + internal void AddReference() { _referenceCount++;