diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 00b4a38..d32d991 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -103,6 +103,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 + bool useDivisorCycles = false; UInt128 divisor = UInt128.Zero; // Device routing bool useGpuCycles = true; @@ -184,6 +185,13 @@ internal static class Program { divisor = UInt128.Parse(arg.AsSpan(arg.IndexOf('=') + 1)); } + else if (arg.StartsWith("--use-divisor-cycles=", StringComparison.OrdinalIgnoreCase)) + { + if (bool.TryParse(arg.AsSpan(arg.IndexOf('=') + 1), out bool parsedBool)) + { + useDivisorCycles = parsedBool; + } + } else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) { if (ulong.TryParse(arg.AsSpan(arg.IndexOf('=') + 1), out parsedLimit)) @@ -501,6 +509,7 @@ internal static class Program _byDivisorTester = new MersenneNumberDivisorByDivisorGpuTester { GpuBatchSize = scanBatchSize, + UseDivisorCycles = useDivisorCycles, }; } @@ -1277,6 +1286,7 @@ internal static class Program Console.WriteLine(" --divisor-cycles-batch-size= batch size for cycles generation (default 512)"); Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation"); Console.WriteLine(" --divisor-cycles-limit= cycle search iterations when --mersenne=divisor"); + Console.WriteLine(" --use-divisor-cycles=true|false enable divisor cycle filtering in --mersenne=bydivisor (default false)"); Console.WriteLine(" --use-order test primality via q order"); Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU"); diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 347a13d..c9ff1b4 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ILGPU; using ILGPU.Runtime; +using PerfectNumbers.Core; namespace PerfectNumbers.Core.Gpu; @@ -16,6 +17,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester private ulong _divisorLimit; private bool _isConfigured; private ulong _lastStatusDivisor; + private bool _useDivisorCycles; public readonly struct MontgomeryDivisorData { @@ -47,6 +49,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester set => _gpuBatchSize = Math.Max(1, value); } + public bool UseDivisorCycles + { + get => _useDivisorCycles; + set => _useDivisorCycles = value; + } + public void ConfigureFromMaxPrime(ulong maxPrime) { lock (_sync) @@ -91,12 +99,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester } } - private bool CheckDivisors(ulong prime, ulong allowedMax, out ulong lastProcessed, out bool coveredRange) - { - allowedMax = Math.Min(allowedMax, _divisorLimit); - lastProcessed = 0UL; + private bool CheckDivisors(ulong prime, ulong allowedMax, out ulong lastProcessed, out bool coveredRange) + { + allowedMax = Math.Min(allowedMax, _divisorLimit); + lastProcessed = 0UL; - if (allowedMax < 3UL) + if (allowedMax < 3UL) { coveredRange = true; return false; @@ -118,40 +126,56 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester bool processedAll = false; ulong divisor = 3UL; - try - { - while (divisor <= allowedMax) - { - int batchSize = 0; - bool reachedEndInBatch = false; + try + { + bool useCycles = _useDivisorCycles; + MersenneDivisorCycles? divisorCycles = useCycles ? MersenneDivisorCycles.Shared : null; + while (divisor <= allowedMax) + { + int batchSize = 0; + bool reachedEndInBatch = false; - while (batchSize < batchCapacity && divisor <= allowedMax) - { - ulong current = divisor; - divisors[batchSize++] = current; + while (batchSize < batchCapacity && divisor <= allowedMax) + { + ulong current = divisor; + ulong next = current + 2UL; + bool include = true; + if (useCycles) + { + ulong cycle = divisorCycles!.GetCycle(current); + if (cycle == 0UL || prime % cycle != 0UL) + { + include = false; + lastProcessed = current; + ReportStatus(lastProcessed); + } + } - ulong next = current + 2UL; - ReportStatus(lastProcessed); - if (next <= current) - { - reachedEndInBatch = true; - break; - } + if (include) + { + divisors[batchSize++] = current; + } - if (next > allowedMax) - { - divisor = next; - reachedEndInBatch = true; - break; - } + if (next <= current) + { + reachedEndInBatch = true; + break; + } - divisor = next; - } + if (next > allowedMax) + { + divisor = next; + reachedEndInBatch = true; + break; + } - if (batchSize == 0) - { - break; - } + divisor = next; + } + + if (batchSize == 0) + { + break; + } Span divisorDataSpan = divisorData.AsSpan(0, batchSize); var divisorView = divisorsBuffer.View.SubView(0, batchSize); @@ -269,63 +293,108 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester private readonly Accelerator _accelerator; private readonly Action, ArrayView> _kernel; private readonly MemoryBuffer1D _primesBuffer; - private readonly MemoryBuffer1D _hitsBuffer; - private readonly ulong[] _primesHost; - private readonly byte[] _hitsHost; - private bool _disposed; + private readonly MemoryBuffer1D _hitsBuffer; + private readonly ulong[] _primesHost; + private readonly byte[] _hitsHost; + private readonly int[] _indexHost; + private bool _disposed; - internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) - { - _owner = owner; + internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) + { + _owner = owner; _lease = GpuContextPool.RentPreferred(preferCpu: false); _accelerator = _lease.Accelerator; _kernel = owner.GetKernelByPrime(_accelerator); - _primesBuffer = _accelerator.Allocate1D(owner._gpuBatchSize); - _hitsBuffer = _accelerator.Allocate1D(owner._gpuBatchSize); - _primesHost = ArrayPool.Shared.Rent(owner._gpuBatchSize); - _hitsHost = ArrayPool.Shared.Rent(owner._gpuBatchSize); - } + _primesBuffer = _accelerator.Allocate1D(owner._gpuBatchSize); + _hitsBuffer = _accelerator.Allocate1D(owner._gpuBatchSize); + _primesHost = ArrayPool.Shared.Rent(owner._gpuBatchSize); + _hitsHost = ArrayPool.Shared.Rent(owner._gpuBatchSize); + _indexHost = ArrayPool.Shared.Rent(owner._gpuBatchSize); + } - public void CheckDivisor(ulong divisor, ReadOnlySpan primes, Span hits) - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(DivisorScanSession)); - } + public void CheckDivisor(ulong divisor, ReadOnlySpan primes, Span hits) + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(DivisorScanSession)); + } - if (primes.Length == 0) - { - return; - } + if (primes.Length == 0) + { + return; + } - int offset = 0; - int consoleStatus = 0; + int offset = 0; + int consoleStatus = 0; + bool useCycles = _owner._useDivisorCycles; + MersenneDivisorCycles? divisorCycles = useCycles ? MersenneDivisorCycles.Shared : null; + ulong cycle = useCycles ? divisorCycles!.GetCycle(divisor) : 0UL; MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor); while (offset < primes.Length) { - int batchSize = Math.Min(_owner._gpuBatchSize, primes.Length - offset); - Span primeSpan = _primesHost.AsSpan(0, batchSize); - primes.Slice(offset, batchSize).CopyTo(primeSpan); + int batchSize = Math.Min(_owner._gpuBatchSize, primes.Length - offset); + Span primeSpan = _primesHost.AsSpan(0, batchSize); + primes.Slice(offset, batchSize).CopyTo(primeSpan); - if (++consoleStatus == PerfectNumberConstants.ConsoleInterval) - { - Console.WriteLine($"...processed by-divisor candidate = {primes[0]}"); - consoleStatus = 0; - } + if (++consoleStatus == PerfectNumberConstants.ConsoleInterval) + { + Console.WriteLine($"...processed by-divisor candidate = {primes[0]}"); + consoleStatus = 0; + } - var primesView = _primesBuffer.View.SubView(0, batchSize); - var hitsView = _hitsBuffer.View.SubView(0, batchSize); + Span indexSpan = _indexHost.AsSpan(0, batchSize); + Span hitSpan = _hitsHost.AsSpan(0, batchSize); + Span hitsSlice = hits.Slice(offset, batchSize); + int gpuCount = 0; - primesView.CopyFromCPU(ref MemoryMarshal.GetReference(primeSpan), batchSize); - _kernel(batchSize, divisorData, primesView, hitsView); - _accelerator.Synchronize(); + if (useCycles) + { + for (int i = 0; i < batchSize; i++) + { + ulong primeValue = primeSpan[i]; + if (cycle == 0UL || primeValue % cycle != 0UL) + { + hitsSlice[i] = 0; + continue; + } - Span hitSpan = _hitsHost.AsSpan(0, batchSize); - hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitSpan), batchSize); - hitSpan.CopyTo(hits.Slice(offset, batchSize)); - offset += batchSize; - } - } + indexSpan[gpuCount] = i; + primeSpan[gpuCount] = primeValue; + gpuCount++; + } + + if (gpuCount > 0) + { + var primesView = _primesBuffer.View.SubView(0, gpuCount); + var hitsView = _hitsBuffer.View.SubView(0, gpuCount); + + primesView.CopyFromCPU(ref MemoryMarshal.GetReference(primeSpan.Slice(0, gpuCount)), gpuCount); + _kernel(gpuCount, divisorData, primesView, hitsView); + _accelerator.Synchronize(); + + hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitSpan.Slice(0, gpuCount)), gpuCount); + for (int i = 0; i < gpuCount; i++) + { + hitsSlice[indexSpan[i]] = hitSpan[i]; + } + } + } + else + { + var primesView = _primesBuffer.View.SubView(0, batchSize); + var hitsView = _hitsBuffer.View.SubView(0, batchSize); + + primesView.CopyFromCPU(ref MemoryMarshal.GetReference(primeSpan), batchSize); + _kernel(batchSize, divisorData, primesView, hitsView); + _accelerator.Synchronize(); + + hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitSpan), batchSize); + hitSpan.CopyTo(hitsSlice); + } + + offset += batchSize; + } + } public void Dispose() { @@ -334,14 +403,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester return; } - _disposed = true; - ArrayPool.Shared.Return(_primesHost, clearArray: true); - ArrayPool.Shared.Return(_hitsHost, clearArray: true); - _primesBuffer.Dispose(); - _hitsBuffer.Dispose(); - _lease.Dispose(); - } - } + _disposed = true; + ArrayPool.Shared.Return(_primesHost, clearArray: true); + ArrayPool.Shared.Return(_hitsHost, clearArray: true); + ArrayPool.Shared.Return(_indexHost, clearArray: true); + _primesBuffer.Dispose(); + _hitsBuffer.Dispose(); + _lease.Dispose(); + } + } private static ulong Pow2Mod(ulong exponent, MontgomeryDivisorData divisor) {