From 3d458fc6dae28541c92fb157edd7b0ecd1d4748b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 26 Oct 2025 22:28:04 +0100 Subject: [PATCH] Use precomputed small prime squares in GPU sieve --- .../Gpu/GpuKernelCompilationTests.cs | 4 ++-- .../Gpu/Kernels/PrimeTesterKernels.cs | 9 +++++++-- PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs | 6 ++++-- PerfectNumbers.Core/PrimeTester.cs | 12 +++++++++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/PerfectNumbers.Core.Tests/Gpu/GpuKernelCompilationTests.cs b/PerfectNumbers.Core.Tests/Gpu/GpuKernelCompilationTests.cs index 1a8b1ae..9f83071 100644 --- a/PerfectNumbers.Core.Tests/Gpu/GpuKernelCompilationTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/GpuKernelCompilationTests.cs @@ -121,7 +121,7 @@ public class GpuKernelCompilationTests private static readonly Action, ArrayView1D> MersenneDivisorCyclesKernel = DivisorCycleKernels.GpuDivisorCycleKernel; - private static readonly Action, ArrayView, ArrayView> PrimeTesterSmallPrimeKernel = + private static readonly Action, ArrayView, ArrayView, ArrayView> PrimeTesterSmallPrimeKernel = PrimeTesterKernels.SmallPrimeSieveKernel; private static readonly Action, ArrayView> PrimeTesterSharesFactorKernel = @@ -336,7 +336,7 @@ public class GpuKernelCompilationTests private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator) { - var kernel = accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView>(PrimeTesterSmallPrimeKernel); + var kernel = accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView>(PrimeTesterSmallPrimeKernel); _ = kernel; } diff --git a/PerfectNumbers.Core/Gpu/Kernels/PrimeTesterKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/PrimeTesterKernels.cs index 1d284e6..9ad7ee7 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/PrimeTesterKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/PrimeTesterKernels.cs @@ -7,7 +7,12 @@ namespace PerfectNumbers.Core.Gpu; internal static class PrimeTesterKernels { // GPU kernel: small-prime sieve only. Returns 1 if passes sieve (probable prime), 0 otherwise. - public static void SmallPrimeSieveKernel(Index1D index, ArrayView numbers, ArrayView smallPrimes, ArrayView results) + public static void SmallPrimeSieveKernel( + Index1D index, + ArrayView numbers, + ArrayView smallPrimes, + ArrayView smallPrimeSquares, + ArrayView results) { ulong n = numbers[index]; @@ -28,7 +33,7 @@ internal static class PrimeTesterKernels for (int i = 0; i < length; i++) { ulong prime = smallPrimes[i]; - ulong primeSquare = (ulong)prime * prime; + ulong primeSquare = smallPrimeSquares[i]; bool withinRange = primeSquare <= n; ulong remainder = n % prime; bool divides = remainder == 0UL; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 4e21cf3..2bbe8b9 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -52,8 +52,9 @@ internal static partial class PrimeOrderCalculator private readonly PrimeTester.KernelState.ScratchBuffers _primeScratch; private readonly ulong[] _primeHostBuffer; private readonly byte[] _primeFlagBuffer; - private readonly Action, ArrayView, ArrayView> _primeKernel; + private readonly Action, ArrayView, ArrayView, ArrayView> _primeKernel; private readonly ArrayView1D _primeDevicePrimes; + private readonly ArrayView1D _primeDevicePrimeSquares; private PartialFactorGpuContext( GpuKernelLease lease, @@ -73,6 +74,7 @@ internal static partial class PrimeOrderCalculator _primeFlagBuffer = primeFlagBuffer; _primeKernel = primeState.Kernel; _primeDevicePrimes = primeState.DevicePrimes.View; + _primeDevicePrimeSquares = primeState.DevicePrimeSquares.View; } public Accelerator Accelerator => _lease.Accelerator; @@ -109,7 +111,7 @@ internal static partial class PrimeOrderCalculator ArrayView1D inputSlice = PrimeDeviceInput.View.SubView(0, 1); inputSlice.CopyFromCPU(ref host[0], 1); ArrayView1D outputSlice = PrimeDeviceOutput.View.SubView(0, 1); - _primeKernel(1, inputSlice.AsContiguous(), _primeDevicePrimes.AsContiguous(), outputSlice.AsContiguous()); + _primeKernel(1, inputSlice.AsContiguous(), _primeDevicePrimes.AsContiguous(), _primeDevicePrimeSquares.AsContiguous(), outputSlice.AsContiguous()); Accelerator.Synchronize(); Span flags = PrimeFlagSpan; outputSlice.CopyToCPU(ref flags[0], 1); diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 2d95f65..09b12c7 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -158,6 +158,7 @@ public sealed class PrimeTester var inputView = deviceInput.View; var outputView = deviceOutput.View; var primesView = state.DevicePrimes.View; + var primeSquaresView = state.DevicePrimeSquares.View; var stagingSpan = stagingBuffer; ref ulong stagingRef = ref MemoryMarshal.GetReference(stagingSpan); @@ -175,7 +176,7 @@ public sealed class PrimeTester inputSlice.CopyFromCPU(ref stagingRef, count); var outputSlice = outputView.SubView(0, count); - state.Kernel(count, inputSlice, primesView, outputSlice); + state.Kernel(count, inputSlice, primesView, primeSquaresView, outputSlice); accelerator.Synchronize(); outputSlice.CopyToCPU(ref results[pos], count); @@ -272,9 +273,10 @@ public sealed class PrimeTester // Per-accelerator GPU state for prime sieve (kernel + uploaded primes). internal sealed class KernelState { - public Action, ArrayView, ArrayView> Kernel { get; } + public Action, ArrayView, ArrayView, ArrayView> Kernel { get; } public Action, ulong, ArrayView> HeuristicTrialDivisionKernel { get; } public MemoryBuffer1D DevicePrimes { get; } + public MemoryBuffer1D DevicePrimeSquares { get; } private readonly Accelerator _accel; private readonly System.Collections.Concurrent.ConcurrentBag _scratchPool = []; // TODO: Replace this ConcurrentBag with the lock-free ring buffer variant validated in @@ -285,12 +287,15 @@ public sealed class PrimeTester { _accel = accelerator; // Compile once per accelerator and upload primes once. - Kernel = accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); + Kernel = accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); HeuristicTrialDivisionKernel = accelerator.LoadAutoGroupedStreamKernel, ulong, ArrayView>(PrimeTesterKernels.HeuristicTrialDivisionKernel); var primes = PrimesGenerator.SmallPrimes; + var primeSquares = PrimesGenerator.SmallPrimesPow2; DevicePrimes = accelerator.Allocate1D(primes.Length); + DevicePrimeSquares = accelerator.Allocate1D(primeSquares.Length); DevicePrimes.View.CopyFromCPU(primes); + DevicePrimeSquares.View.CopyFromCPU(primeSquares); } internal sealed class ScratchBuffers : IDisposable @@ -344,6 +349,7 @@ public sealed class PrimeTester } DevicePrimes.Dispose(); + DevicePrimeSquares.Dispose(); } }