From 85a90c70bfd40d0e5e8f5150f0c2629085cb85f5 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Tue, 2 Dec 2025 23:36:18 +0100 Subject: [PATCH] --gpu-ration command-line argument added. AscendingDivisorRemainderStepper implemented in PrimeTester.IsPrimeCpu. PrimeOrderCalculator.RunOnCpu adjusted for low GPU ratios to prevent infinite increase of the counter. --- EvenPerfectBitScanner/CliArguments.cs | 36 +- EvenPerfectBitScanner/Program.cs | 15 +- EvenPerfectBitScanner/Utf8CliParser.cs | 15 + .../AscendingDivisorRemainderStepper.cs | 38 ++ PerfectNumbers.Core/Gpu/AcceleratorPool.cs | 8 - .../Gpu/GpuPrimeWorkLimiter.cs | 2 +- PerfectNumbers.Core/PerfectNumberConstants.cs | 10 +- .../PrimeOrderCalculator.Cpu.cs | 35 +- PerfectNumbers.Core/PrimeTester.cs | 349 +++++++++--------- 9 files changed, 296 insertions(+), 212 deletions(-) create mode 100644 PerfectNumbers.Core/AscendingDivisorRemainderStepper.cs diff --git a/EvenPerfectBitScanner/CliArguments.cs b/EvenPerfectBitScanner/CliArguments.cs index 426ef70..9e8bb85 100644 --- a/EvenPerfectBitScanner/CliArguments.cs +++ b/EvenPerfectBitScanner/CliArguments.cs @@ -51,8 +51,9 @@ internal readonly struct CliArguments internal readonly double ZeroFractionConjecture; internal readonly int MaxZeroConjecture; internal readonly ulong MinK; + internal readonly int GpuRatio; - private CliArguments( + private CliArguments( ulong startPrime, bool startPrimeProvided, UInt128 maxPrimeLimit, @@ -95,7 +96,8 @@ internal readonly struct CliArguments double zeroFractionHard, double zeroFractionConjecture, int maxZeroConjecture, - ulong minK) + ulong minK, + int gpuRatio) { StartPrime = startPrime; StartPrimeProvided = startPrimeProvided; @@ -140,6 +142,7 @@ internal readonly struct CliArguments ZeroFractionConjecture = zeroFractionConjecture; MaxZeroConjecture = maxZeroConjecture; MinK = minK; + GpuRatio = gpuRatio; } internal bool UseFilter => !TestMode && !string.IsNullOrEmpty(FilterFile); @@ -189,6 +192,7 @@ internal readonly struct CliArguments double zeroFractionConjecture = -1.0; int maxZeroConjecture = -1; ulong minK = 1UL; + int gpuRatio = PerfectNumberConstants.GpuRatio; foreach (string argument in args) { @@ -239,6 +243,12 @@ internal readonly struct CliArguments continue; } + if (argument.StartsWith("--gpu-ratio=", StringComparison.OrdinalIgnoreCase)) + { + gpuRatio = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-ratio=".Length))); + continue; + } + if (argument.StartsWith("--threads=", StringComparison.OrdinalIgnoreCase)) { threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--threads=".Length))); @@ -590,7 +600,8 @@ internal readonly struct CliArguments zeroFractionHard, zeroFractionConjecture, maxZeroConjecture, - minK); + minK, + gpuRatio); } internal static void PrintHelp() @@ -598,22 +609,23 @@ internal readonly struct CliArguments Console.WriteLine("Usage: EvenPerfectBitScanner [options]"); Console.WriteLine(); Console.WriteLine("Options:"); + Console.WriteLine(" --block-size= values processed per thread batch"); Console.WriteLine(" --prime= starting exponent (p)"); Console.WriteLine(" --max-prime= inclusive upper bound for primes from filter files"); - Console.WriteLine(" --increment=bit|add exponent increment method"); + Console.WriteLine(" --increment=bit|add exponent increment method (default add)"); Console.WriteLine(" --threads= number of worker threads"); - Console.WriteLine(" --block-size= values processed per thread batch"); Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method"); - Console.WriteLine(" --residue-max-k= max k for residue Mersenne test (q = 2*p*k + 1)"); + Console.WriteLine(" --residue-max-k= max k for residue Mersenne test (q = 2*p*k + 1)"); Console.WriteLine(" --mersenne-device=cpu|gpu|hybrid Device for Mersenne method (default gpu)"); - Console.WriteLine(" --primes-device=cpu|gpu Device for prime-scan kernels (default gpu)"); - Console.WriteLine(" --gpu-prime-batch= Batch size for GPU primality sieve (default 262144)"); - Console.WriteLine(" --order-device=cpu|gpu|hybrid Device for order computations (default gpu)"); - Console.WriteLine(" --ntt=reference|staged GPU NTT backend (default staged)"); - Console.WriteLine(" --mod-reduction=auto|uint128|mont64|barrett128 staged NTT reduction (default auto)"); + Console.WriteLine(" --primes-device=cpu|gpu device for prime-scan kernels (default gpu)"); + Console.WriteLine(" --gpu-prime-batch= batch size for GPU primality sieve (default 262144)"); + Console.WriteLine($" --gpu-ratio= ratio of GPU operations vs CPU in hybrid device mode (default {PerfectNumberConstants.GpuRatio})"); Console.WriteLine(" --gpu-prime-threads= max concurrent GPU prime checks (default 1)"); - Console.WriteLine(" --ll-slice= Lucas–Lehmer iterations per slice (default 32)"); Console.WriteLine(" --gpu-scan-batch= GPU q-scan batch size (default 2_097_152)"); + Console.WriteLine(" --order-device=cpu|gpu|hybrid Device for order computations (default gpu)"); + Console.WriteLine(" --ll-slice= Lucas–Lehmer iterations per slice (default 32)"); + Console.WriteLine(" --mod-reduction=auto|uint128|mont64|barrett128 staged NTT reduction (default auto)"); + Console.WriteLine(" --ntt=reference|staged GPU NTT backend (default staged)"); Console.WriteLine(" --order-warmup-limit= Warm-up order candidates (default 5_000_000)"); Console.WriteLine(" --rle-blacklist= enable RLE blacklist for p (hard filter up to --rle-hard-max)"); Console.WriteLine(" --rle-hard-max=

apply RLE blacklist only for p <= this (default ulong.MaxValue = no limit)"); diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index bdfb913..6f8ec51 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -51,22 +51,27 @@ internal static class Program return; } - NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend; - NttGpuMath.ReductionMode = _cliArguments.ModReductionMode; + Console.WriteLine("Initializing"); + int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads); + int threadCount = Math.Max(1, _cliArguments.ThreadCount); + PerfectNumberConstants.GpuRatio = _cliArguments.GpuRatio; + PerfectNumberConstants.RollingAccelerators = Math.Min(Math.Min(PerfectNumberConstants.RollingAccelerators, gpuPrimeThreads), threadCount); + + // NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend; + // NttGpuMath.ReductionMode = _cliArguments.ModReductionMode; _runPrimesOnCpu = _cliArguments.ForcePrimeKernelsOnCpu; ulong currentP = _cliArguments.StartPrime; ulong remainder = currentP % 6UL; bool startPrimeProvided = _cliArguments.StartPrimeProvided; - int threadCount = Math.Max(1, _cliArguments.ThreadCount); int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch); // int gpuPrimeThreads = SharedGpuContext.Device.MaxNumThreads; - int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads); + Console.WriteLine("Setting up parameters"); UnboundedTaskScheduler.ConfigureThreadCount(threadCount); PrimeTester.GpuBatchSize = gpuPrimeBatch; GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads); Console.WriteLine("Warming up GPU kernels"); - PrimeTester.WarmUpGpuKernels(1024); + PrimeTester.WarmUpGpuKernels(PerfectNumberConstants.RollingAccelerators); // PrimeTester.WarmUpGpuKernels(gpuPrimeThreads >> 4); Console.WriteLine("Starting up threads..."); _ = UnboundedTaskScheduler.Instance; diff --git a/EvenPerfectBitScanner/Utf8CliParser.cs b/EvenPerfectBitScanner/Utf8CliParser.cs index a2b4cc7..b7706a2 100644 --- a/EvenPerfectBitScanner/Utf8CliParser.cs +++ b/EvenPerfectBitScanner/Utf8CliParser.cs @@ -29,11 +29,26 @@ internal static class Utf8CliParser return result; } + public static uint ParseUInt32(ReadOnlySpan value) + { + if (!TryParseUInt32(value, out uint result)) + { + ThrowFormatException(value); + } + + return result; + } + public static bool TryParseUInt64(ReadOnlySpan value, out ulong result) { return TryParse(value, static (ReadOnlySpan source, out ulong parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result); } + public static bool TryParseUInt32(ReadOnlySpan value, out uint result) + { + return TryParse(value, static (ReadOnlySpan source, out uint parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result); + } + public static bool TryParseInt32(ReadOnlySpan value, out int result) { return TryParse(value, static (ReadOnlySpan source, out int parsed, out int consumed) => Utf8Parser.TryParse(source, out parsed, out consumed), out result); diff --git a/PerfectNumbers.Core/AscendingDivisorRemainderStepper.cs b/PerfectNumbers.Core/AscendingDivisorRemainderStepper.cs new file mode 100644 index 0000000..5297db7 --- /dev/null +++ b/PerfectNumbers.Core/AscendingDivisorRemainderStepper.cs @@ -0,0 +1,38 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core; + +public struct AscendingDivisorRemainderStepper +{ + private readonly ulong dividend; + private ulong maxDivisor; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AscendingDivisorRemainderStepper(ulong value) + { + dividend = value; + ulong root = (ulong)Math.Sqrt(value); + if ((UInt128)root * root < value) + { + root++; + } + + maxDivisor = root; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool ShouldContinue(ulong divisor) => divisor <= maxDivisor; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Divides(ulong divisor) + { + ulong quotient = dividend / divisor; + ulong remainder = dividend - (quotient * divisor); + if (quotient < maxDivisor) + { + maxDivisor = quotient; + } + + return remainder == 0UL; + } +} diff --git a/PerfectNumbers.Core/Gpu/AcceleratorPool.cs b/PerfectNumbers.Core/Gpu/AcceleratorPool.cs index 4cfed88..ca16355 100644 --- a/PerfectNumbers.Core/Gpu/AcceleratorPool.cs +++ b/PerfectNumbers.Core/Gpu/AcceleratorPool.cs @@ -58,19 +58,11 @@ namespace PerfectNumbers.Core.Gpu var device = context.GetPreferredDevice(false); Context = context; Device = device; - int acceleratorsInContext = 0; int acceleratorIndex = 0; Accelerators = [.. Enumerable.Range(0, capacity).Select(_ => { Console.WriteLine($"Preparing accelerator {acceleratorIndex++}"); Accelerator accelerator = device.CreateAccelerator(context); - acceleratorsInContext++; - if (acceleratorsInContext >= 128) - { - context = Context.CreateDefault(); - device = context.GetPreferredDevice(false); - acceleratorsInContext = 0; - } return accelerator; })]; diff --git a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs index 80c343c..cf154e5 100644 --- a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs +++ b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs @@ -4,7 +4,7 @@ namespace PerfectNumbers.Core.Gpu; public static class GpuPrimeWorkLimiter { - private static readonly TimeSpan _pauseBetweenChecks = TimeSpan.FromSeconds(2); + private static readonly TimeSpan _pauseBetweenChecks = TimeSpan.FromSeconds(10); private static PollingSemaphore _semaphore = new(1, _pauseBetweenChecks); private static int _currentLimit = 1; diff --git a/PerfectNumbers.Core/PerfectNumberConstants.cs b/PerfectNumbers.Core/PerfectNumberConstants.cs index 40ea78d..f5c552e 100644 --- a/PerfectNumbers.Core/PerfectNumberConstants.cs +++ b/PerfectNumbers.Core/PerfectNumberConstants.cs @@ -1,3 +1,5 @@ +#pragma warning disable CA2211 // Non-constant fields should not be visible + namespace PerfectNumbers.Core; public static class PerfectNumberConstants @@ -8,14 +10,14 @@ public static class PerfectNumberConstants public const int DefaultSmallPrimeFactorSlotCount = 64; public const int DefaultSpecialMaxFactorCapacity = 1024; public const ulong ExtraDivisorCycleSearchLimit = 64UL; - public const int GpuFrequency = 16384 - 1; - public const int MaxQForDivisorCycles = 4_000_000; + public static int GpuRatio = 16384 - 1; + public const int MaxQForDivisorCycles = 4_000_000; public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000; public const int MaxOddPowersCount = 128; public const int PooledArrayThreshold = 64; public const int Pow2WindowSize = 8; - public static int RollingAccelerators = 298; //SharedGpuContext.Device.MaxNumThreadsPerGroup; - public const int ThreadsByAccelerator = 8; + public static int RollingAccelerators = 160; //SharedGpuContext.Device.MaxNumThreadsPerGroup; + public const int ThreadsByAccelerator = 1; public const int ByDivisorStateSaveInterval = 10_000; public const string ByDivisorStateDirectory = "Checks"; // TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 4e8fe86..609d720 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -2499,17 +2499,40 @@ internal static partial class PrimeOrderCalculator private static bool RunOnCpu() { - int cpuCount = Atomic.Add(ref _cpuCount, 1); - if (cpuCount == PerfectNumberConstants.GpuFrequency) + if (PerfectNumberConstants.GpuRatio == 0) { - Atomic.Add(ref _cpuCount, -PerfectNumberConstants.GpuFrequency); + return false; } - else if (cpuCount > PerfectNumberConstants.GpuFrequency) + + if (PerfectNumberConstants.GpuRatio == 1) { - cpuCount -= PerfectNumberConstants.GpuFrequency; + while (true) + { + if (Atomic.CompareExchange(ref _cpuCount, 0, 1) == 0) + { + return true; + } + + if (Atomic.CompareExchange(ref _cpuCount, 1, 0) == 1) + { + return false; + } + + Thread.Yield(); + } } - return cpuCount != PerfectNumberConstants.GpuFrequency; + int cpuCount = Atomic.Add(ref _cpuCount, 1); + if (cpuCount == PerfectNumberConstants.GpuRatio) + { + Atomic.Add(ref _cpuCount, -PerfectNumberConstants.GpuRatio); + } + else if (cpuCount > PerfectNumberConstants.GpuRatio) + { + cpuCount -= PerfectNumberConstants.GpuRatio; + } + + return cpuCount != PerfectNumberConstants.GpuRatio; } private static void PopulateSmallPrimeFactorsCpu( diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 2cf98d9..eee2d23 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using System.Runtime.CompilerServices; using ILGPU; @@ -8,219 +9,215 @@ namespace PerfectNumbers.Core; public sealed class PrimeTester { - public PrimeTester() - { - } + public PrimeTester() + { + } - [ThreadStatic] - private static PrimeTester? _tester; + [ThreadStatic] + private static PrimeTester? _tester; - public static PrimeTester Exclusive => _tester ??= new(); + public static PrimeTester Exclusive => _tester ??= new(); - public static bool IsPrimeCpu(ulong n) - { - if (n <= 1UL) - { - return false; - } + public static bool IsPrimeCpu(ulong n) + { + if (n <= 1UL) + { + return false; + } - if (n == 2UL) - { - throw new InvalidOperationException("PrimeTester.IsPrime encountered the sentinel input 2."); - } + if (n == 2UL) + { + throw new InvalidOperationException("PrimeTester.IsPrime encountered the sentinel input 2."); + } - bool isOdd = (n & 1UL) != 0UL; - bool result = isOdd; + if ((n & 1UL) == 0UL) + { + return false; + } - bool requiresTrialDivision = result && n >= 7UL; + if (n < 7UL) + { + return true; + } - if (requiresTrialDivision) - { - // EvenPerfectBitScanner streams exponents starting at 136,279,841, so the Mod10/GCD guard never fires on the - // production path. Leave the logic commented out as instrumentation for diagnostic builds. - // bool sharesMaxExponentFactor = n.Mod10() == 1UL && SharesFactorWithMaxExponent(n); - // result &= !sharesMaxExponentFactor; + var remainderStepper = new AscendingDivisorRemainderStepper(n); + uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes; + int smallPrimeDivisorsLength = smallPrimeDivisors.Length; - if (result) - { - uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes; - ulong[] smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2; - int smallPrimeDivisorsLength = smallPrimeDivisors.Length; - for (int i = 0; i < smallPrimeDivisorsLength; i++) - { - if (smallPrimeDivisorsMul[i] > n) - { - break; - } + for (int i = 1; i < smallPrimeDivisorsLength; i++) + { + uint divisor = smallPrimeDivisors[i]; - if (n % smallPrimeDivisors[i] == 0) - { - result = false; - break; - } - } - } - } + if (!remainderStepper.ShouldContinue(divisor)) + { + break; + } - return result; - } + if (remainderStepper.Divides(divisor)) + { + return false; + } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n) - { - byte flag = 0; - var inputView = gpu.InputView; - var outputView = gpu.OutputByteView; + return true; + } - int acceleratorIndex = gpu.AcceleratorIndex; - AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); - inputView.CopyFromCPU(stream, ref n, 1); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n) + { + byte flag = 0; + var inputView = gpu.InputView; + var outputView = gpu.OutputByteView; - var kernelLauncher = gpu.SmallPrimeSieveKernelLauncher; - - kernelLauncher( - stream, - 1, - inputView, - gpu.DevicePrimesLastOne, - gpu.DevicePrimesLastSeven, - gpu.DevicePrimesLastThree, - gpu.DevicePrimesLastNine, - gpu.DevicePrimesPow2LastOne, - gpu.DevicePrimesPow2LastSeven, - gpu.DevicePrimesPow2LastThree, - gpu.DevicePrimesPow2LastNine, - outputView); + int acceleratorIndex = gpu.AcceleratorIndex; + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); + inputView.CopyFromCPU(stream, ref n, 1); - outputView.CopyToCPU(stream, ref flag, 1); - stream.Synchronize(); - AcceleratorStreamPool.Return(acceleratorIndex, stream); + var kernelLauncher = gpu.SmallPrimeSieveKernelLauncher; + + kernelLauncher( + stream, + 1, + inputView, + gpu.DevicePrimesLastOne, + gpu.DevicePrimesLastSeven, + gpu.DevicePrimesLastThree, + gpu.DevicePrimesLastNine, + gpu.DevicePrimesPow2LastOne, + gpu.DevicePrimesPow2LastSeven, + gpu.DevicePrimesPow2LastThree, + gpu.DevicePrimesPow2LastNine, + outputView); - return flag != 0; - } + outputView.CopyToCPU(stream, ref flag, 1); + stream.Synchronize(); + AcceleratorStreamPool.Return(acceleratorIndex, stream); - public static int GpuBatchSize { get; set; } = 262_144; + return flag != 0; + } - private static readonly object GpuWarmUpLock = new(); - private static int WarmedGpuLeaseCount; + public static int GpuBatchSize { get; set; } = 262_144; - public static void WarmUpGpuKernels(int threadCount) - { - int target = threadCount >> 2; - if (target == 0) - { - target = threadCount; - } + private static readonly object GpuWarmUpLock = new(); + private static int WarmedGpuLeaseCount; - lock (GpuWarmUpLock) - { - if (target <= WarmedGpuLeaseCount) - { - return; - } + public static void WarmUpGpuKernels(int threadCount) + { + int target = threadCount; + if (target == 0) + { + target = threadCount; + } - PrimeOrderCalculatorAccelerator.WarmUp(); - WarmedGpuLeaseCount = target; - } - } + lock (GpuWarmUpLock) + { + if (target <= WarmedGpuLeaseCount) + { + return; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsPrimeBatchGpu(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan values, Span results) - { - // GpuPrimeWorkLimiter.Acquire(); - int acceleratorIndex = gpu.AcceleratorIndex; - int totalLength = values.Length; - int batchSize = GpuBatchSize; + PrimeOrderCalculatorAccelerator.WarmUp(); + WarmedGpuLeaseCount = target; + } + } - var inputView = gpu.InputView; - var outputView = gpu.OutputByteView; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IsPrimeBatchGpu(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan values, Span results) + { + // GpuPrimeWorkLimiter.Acquire(); + int acceleratorIndex = gpu.AcceleratorIndex; + int totalLength = values.Length; + int batchSize = GpuBatchSize; - int pos = 0; - AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); - var kernelLauncher = gpu.SmallPrimeSieveKernelLauncher; + var inputView = gpu.InputView; + var outputView = gpu.OutputByteView; - while (pos < totalLength) - { - int remaining = totalLength - pos; - int count = remaining > batchSize ? batchSize : remaining; + int pos = 0; + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); + var kernelLauncher = gpu.SmallPrimeSieveKernelLauncher; - var valueSlice = values.Slice(pos, count); - inputView.CopyFromCPU(stream, valueSlice); + while (pos < totalLength) + { + int remaining = totalLength - pos; + int count = remaining > batchSize ? batchSize : remaining; - kernelLauncher( - stream, - count, - inputView, - gpu.DevicePrimesLastOne, - gpu.DevicePrimesLastSeven, - gpu.DevicePrimesLastThree, - gpu.DevicePrimesLastNine, - gpu.DevicePrimesPow2LastOne, - gpu.DevicePrimesPow2LastSeven, - gpu.DevicePrimesPow2LastThree, - gpu.DevicePrimesPow2LastNine, - outputView); + var valueSlice = values.Slice(pos, count); + inputView.CopyFromCPU(stream, valueSlice); - var resultSlice = results.Slice(pos, count); - outputView.CopyToCPU(stream, resultSlice); + kernelLauncher( + stream, + count, + inputView, + gpu.DevicePrimesLastOne, + gpu.DevicePrimesLastSeven, + gpu.DevicePrimesLastThree, + gpu.DevicePrimesLastNine, + gpu.DevicePrimesPow2LastOne, + gpu.DevicePrimesPow2LastSeven, + gpu.DevicePrimesPow2LastThree, + gpu.DevicePrimesPow2LastNine, + outputView); - pos += count; - } + var resultSlice = results.Slice(pos, count); + outputView.CopyToCPU(stream, resultSlice); - stream.Synchronize(); - AcceleratorStreamPool.Return(acceleratorIndex, stream); - // GpuPrimeWorkLimiter.Release(); - } + pos += count; + } - // Expose cache clearing for accelerator disposal coordination - public static void ClearGpuCaches() - { - PrimeOrderCalculatorAccelerator.Clear(); - } + stream.Synchronize(); + AcceleratorStreamPool.Return(acceleratorIndex, stream); + // GpuPrimeWorkLimiter.Release(); + } - internal static void DisposeGpuContexts() - { - PrimeOrderCalculatorAccelerator.DisposeAll(); - lock (GpuWarmUpLock) - { - WarmedGpuLeaseCount = 0; - } - } + // Expose cache clearing for accelerator disposal coordination + public static void ClearGpuCaches() + { + PrimeOrderCalculatorAccelerator.Clear(); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool SharesFactorWithMaxExponent(ulong n) - { - // TODO: Replace this on-the-fly GCD probe with the cached factor table derived from - // ResidueComputationBenchmarks so divisor-cycle metadata can short-circuit the test - // instead of recomputing binary GCD for every candidate. - ulong m = (ulong)BitOperations.Log2(n); - return n.BinaryGcd(m) != 1UL; - } + internal static void DisposeGpuContexts() + { + PrimeOrderCalculatorAccelerator.DisposeAll(); + lock (GpuWarmUpLock) + { + WarmedGpuLeaseCount = 0; + } + } - internal static void SharesFactorWithMaxExponentBatch(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan values, Span results) - { - // TODO: Route this batch helper through the shared GPU kernel pool from - // GpuUInt128BinaryGcdBenchmarks so we reuse cached kernels, pinned host buffers, - // and divisor-cycle staging instead of allocating new device buffers per call. - // Check in benchmarks, which implementation was the fastest, is compatible with GPU, - // and implement it. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool SharesFactorWithMaxExponent(ulong n) + { + // TODO: Replace this on-the-fly GCD probe with the cached factor table derived from + // ResidueComputationBenchmarks so divisor-cycle metadata can short-circuit the test + // instead of recomputing binary GCD for every candidate. + ulong m = (ulong)BitOperations.Log2(n); + return n.BinaryGcd(m) != 1UL; + } - int length = values.Length; + internal static void SharesFactorWithMaxExponentBatch(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan values, Span results) + { + // TODO: Route this batch helper through the shared GPU kernel pool from + // GpuUInt128BinaryGcdBenchmarks so we reuse cached kernels, pinned host buffers, + // and divisor-cycle staging instead of allocating new device buffers per call. + // Check in benchmarks, which implementation was the fastest, is compatible with GPU, + // and implement it. - int acceleratorIndex = gpu.AcceleratorIndex; - gpu.EnsureCapacity(0, length); - var inputBufferView = gpu.InputView; - var resultBufferView = gpu.OutputByteView; - var kernelLauncher = gpu.SharesFactorKernelLauncher; + int length = values.Length; - AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); - inputBufferView.CopyFromCPU(stream, values); + int acceleratorIndex = gpu.AcceleratorIndex; + gpu.EnsureCapacity(0, length); + var inputBufferView = gpu.InputView; + var resultBufferView = gpu.OutputByteView; + var kernelLauncher = gpu.SharesFactorKernelLauncher; - kernelLauncher(stream, length, inputBufferView, resultBufferView); + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); + inputBufferView.CopyFromCPU(stream, values); - resultBufferView.CopyToCPU(stream, in results); - stream.Synchronize(); + kernelLauncher(stream, length, inputBufferView, resultBufferView); - AcceleratorStreamPool.Return(acceleratorIndex, stream); - } + resultBufferView.CopyToCPU(stream, in results); + stream.Synchronize(); + + AcceleratorStreamPool.Return(acceleratorIndex, stream); + } } \ No newline at end of file