From 0902b18721a64a9060501f4b6c134b26b792ef00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Mon, 13 Oct 2025 01:35:21 +0200 Subject: [PATCH] Add GPU delta routing option for by-divisor mode --- EvenPerfectBitScanner/Program.cs | 33 ++++++- PerfectNumbers.Core/ByDivisorDeltasDevice.cs | 7 ++ ...MersenneNumberDivisorByDivisorCpuTester.cs | 87 ++++++++++++++++++- 3 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 PerfectNumbers.Core/ByDivisorDeltasDevice.cs diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 7cefd12..e813d6b 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -75,6 +75,7 @@ internal static class Program bool useResidue = false; // M_p test via residue divisors (replaces LL/incremental) bool useDivisor = false; // M_p divisibility by specific divisor bool useByDivisor = false; // Iterative divisor scan across primes UInt128 divisor = UInt128.Zero; + ByDivisorDeltasDevice byDivisorDeltasDevice = ByDivisorDeltasDevice.Cpu; // Device routing bool useGpuCycles = true; bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device @@ -182,6 +183,23 @@ internal static class Program { mersenneOnGpu = !arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase); } + else if (arg.StartsWith("--bydivisor-deltas-device=", StringComparison.OrdinalIgnoreCase)) + { + ReadOnlySpan deviceValue = arg.AsSpan(arg.IndexOf('=') + 1); + if (deviceValue.Equals("gpu", StringComparison.OrdinalIgnoreCase)) + { + byDivisorDeltasDevice = ByDivisorDeltasDevice.Gpu; + } + else if (deviceValue.Equals("cpu", StringComparison.OrdinalIgnoreCase)) + { + byDivisorDeltasDevice = ByDivisorDeltasDevice.Cpu; + } + else + { + Console.WriteLine("Invalid --bydivisor-deltas-device value. Expected cpu or gpu."); + return; + } + } else if (arg.StartsWith("--prime-test-limit=", StringComparison.OrdinalIgnoreCase)) { ReadOnlySpan value = arg.AsSpan(arg.IndexOf('=') + 1); @@ -531,6 +549,10 @@ internal static class Program ? new MersenneNumberDivisorByDivisorGpuTester() : new MersenneNumberDivisorByDivisorCpuTester(); _byDivisorTester.BatchSize = scanBatchSize; + if (_byDivisorTester is MersenneNumberDivisorByDivisorCpuTester cpuByDivisorTester) + { + cpuByDivisorTester.DeltasDevice = byDivisorDeltasDevice; + } } // Load RLE blacklist (optional) @@ -557,6 +579,9 @@ internal static class Program Console.WriteLine("Initialization..."); // Compose a results file name that encodes configuration (before opening file) + string byDivisorDeltasLabel = mersenneOnGpu + ? "gpu" + : (byDivisorDeltasDevice == ByDivisorDeltasDevice.Gpu ? "cpu-gpu" : "cpu"); var builtName = BuildResultsFileName( useBitTransform, threadCount, @@ -577,7 +602,8 @@ internal static class Program NttGpuMath.ReductionMode, mersenneOnGpu ? "gpu" : "cpu", (GpuContextPool.ForceCpu ? "cpu" : "gpu"), - orderOnGpu ? "gpu" : "cpu"); + orderOnGpu ? "gpu" : "cpu", + byDivisorDeltasLabel); if (!string.IsNullOrEmpty(_resultsPrefix)) { @@ -984,6 +1010,7 @@ internal static class Program Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation"); Console.WriteLine(" --divisor-cycles-limit= cycle search iterations when --mersenne=divisor"); Console.WriteLine(" --prime-test-limit= Time limit for --mersenne=bydivisor prime checks (e.g. 5s, 500ms)"); + Console.WriteLine(" --bydivisor-deltas-device=cpu|gpu Device for --mersenne=bydivisor residue batches (default: cpu)"); Console.WriteLine(" --use-order test primality via q order"); Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU"); // mod-automaton removed @@ -993,7 +1020,7 @@ internal static class Program Console.WriteLine(" --help, -help, --?, -?, /? show this help message"); } - private static string BuildResultsFileName(bool bitInc, int threads, int block, GpuKernelType kernelType, bool useLucasFlag, bool useDivisorFlag, bool useByDivisorFlag, bool mersenneOnGpu, bool useOrder, bool useModWorkaround, bool useGcd, NttBackend nttBackend, int gpuPrimeThreads, int llSlice, int gpuScanBatch, ulong warmupLimit, ModReductionMode reduction, string mersenneDevice, string primesDevice, string orderDevice) + private static string BuildResultsFileName(bool bitInc, int threads, int block, GpuKernelType kernelType, bool useLucasFlag, bool useDivisorFlag, bool useByDivisorFlag, bool mersenneOnGpu, bool useOrder, bool useModWorkaround, bool useGcd, NttBackend nttBackend, int gpuPrimeThreads, int llSlice, int gpuScanBatch, ulong warmupLimit, ModReductionMode reduction, string mersenneDevice, string primesDevice, string orderDevice, string byDivisorDeltasDevice) { string inc = bitInc ? "bit" : "add"; string mers = useDivisorFlag @@ -1001,7 +1028,7 @@ internal static class Program : (useLucasFlag ? "lucas" : (useByDivisorFlag - ? "bydivisor" + ? $"bydivisor-{byDivisorDeltasDevice}" : (kernelType == GpuKernelType.Pow2Mod ? "pow2mod" : "incremental"))); string ntt = nttBackend == NttBackend.Staged ? "staged" : "reference"; string red = reduction switch { ModReductionMode.Mont64 => "mont64", ModReductionMode.Barrett128 => "barrett128", ModReductionMode.GpuUInt128 => "uint128", _ => "auto" }; diff --git a/PerfectNumbers.Core/ByDivisorDeltasDevice.cs b/PerfectNumbers.Core/ByDivisorDeltasDevice.cs new file mode 100644 index 0000000..2011b25 --- /dev/null +++ b/PerfectNumbers.Core/ByDivisorDeltasDevice.cs @@ -0,0 +1,7 @@ +namespace PerfectNumbers.Core; + +public enum ByDivisorDeltasDevice +{ + Cpu, + Gpu +} diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index d64ef23..4ee09a7 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Collections.Concurrent; using System.Numerics; using PerfectNumbers.Core; +using PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Cpu; @@ -15,20 +16,51 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv private bool _isConfigured; private int _batchSize = 1_024; + private ulong _configuredMaxPrime; + private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu; + private MersenneNumberDivisorByDivisorGpuTester? _gpuDeltasTester; + private bool _gpuTesterConfigured; + public int BatchSize { get => _batchSize; - set => _batchSize = Math.Max(1, value); + set + { + int sanitized = Math.Max(1, value); + _batchSize = sanitized; + + lock (_sync) + { + if (_gpuDeltasTester is not null) + { + _gpuDeltasTester.GpuBatchSize = sanitized; + } + } + } + } + + public ByDivisorDeltasDevice DeltasDevice + { + get => _deltasDevice; + set + { + lock (_sync) + { + _deltasDevice = value; + } + } } public void ConfigureFromMaxPrime(ulong maxPrime) { lock (_sync) { + _configuredMaxPrime = maxPrime; _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); _lastStatusDivisor = 0UL; _isConfigured = true; + _gpuTesterConfigured = false; } } @@ -135,6 +167,31 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } } + internal IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateGpuDeltasSession() + { + MersenneNumberDivisorByDivisorGpuTester tester; + + lock (_sync) + { + if (!_isConfigured) + { + throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + } + + tester = _gpuDeltasTester ??= new MersenneNumberDivisorByDivisorGpuTester(); + + if (!_gpuTesterConfigured) + { + tester.ConfigureFromMaxPrime(_configuredMaxPrime); + _gpuTesterConfigured = true; + } + + tester.GpuBatchSize = _batchSize; + } + + return tester.CreateDivisorSession(); + } + public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() { lock (_sync) @@ -457,6 +514,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv private ulong[]? _cycleRemainders; private ulong[]? _montgomeryDeltas; private int _bufferCapacity; + private IMersenneNumberDivisorByDivisorTester.IDivisorScanSession? _gpuSession; internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner) { @@ -514,6 +572,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } } + if (_owner._deltasDevice == ByDivisorDeltasDevice.Gpu) + { + EnsureGpuSession(); + _gpuSession!.CheckDivisor(divisor, cachedData, divisorCycle, primes, hits); + return; + } + EnsureCapacity(length); Span deltaSpan = _primeDeltas!.AsSpan(0, length); @@ -538,6 +603,25 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } } + private void EnsureGpuSession() + { + if (_gpuSession is null) + { + _gpuSession = _owner.CreateGpuDeltasSession(); + } + } + + private void ReleaseGpuSession() + { + if (_gpuSession is null) + { + return; + } + + _gpuSession.Dispose(); + _gpuSession = null; + } + private void EnsureCapacity(int requiredLength) { if (requiredLength <= 0) @@ -683,6 +767,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } _disposed = true; + ReleaseGpuSession(); _owner.ReturnSession(this); } }