From fb7734528f3e66016c4d93747f8e81acd48907ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 20 Sep 2025 10:48:12 +0200 Subject: [PATCH] Tiny cleanup --- PerfectNumbers.Core/MersenneNumberTester.cs | 458 ++++++++++---------- 1 file changed, 229 insertions(+), 229 deletions(-) diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index d5c667a..c46b970 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -11,63 +11,63 @@ namespace PerfectNumbers.Core; public enum GpuKernelType { - Incremental, - Pow2Mod, + Incremental, + Pow2Mod, } public sealed class MersenneNumberTester( - bool useIncremental = true, - bool useOrderCache = false, - GpuKernelType kernelType = GpuKernelType.Incremental, - bool useModuloWorkaround = false, - bool useOrder = false, - bool useGpuLucas = true, - bool useGpuScan = true, - bool useGpuOrder = true, - bool useResidue = true, - UInt128? maxK = null, - UInt128? residueDivisorSets = null) + bool useIncremental = true, + bool useOrderCache = false, + GpuKernelType kernelType = GpuKernelType.Incremental, + bool useModuloWorkaround = false, + bool useOrder = false, + bool useGpuLucas = true, + bool useGpuScan = true, + bool useGpuOrder = true, + bool useResidue = true, + UInt128? maxK = null, + UInt128? residueDivisorSets = null) { - private readonly bool _useResidue = useResidue; - private readonly bool _useIncremental = useIncremental && !useResidue; - private readonly UInt128 _maxK = maxK ?? (UInt128)5_000_000UL; - private readonly UInt128 _residueDivisorSetCount = residueDivisorSets ?? (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit; - private readonly GpuKernelType _kernelType = kernelType; - private readonly bool _useModuloWorkaround = useModuloWorkaround; - private readonly bool _useGpuLucas = useGpuLucas; - private readonly bool _useGpuScan = useGpuScan; // device for pow2mod/incremental scanning - private readonly bool _useGpuOrder = useGpuOrder; // device for order computations - private static readonly ConcurrentDictionary OrderCache = new(); + private readonly bool _useResidue = useResidue; + private readonly bool _useIncremental = useIncremental && !useResidue; + private readonly UInt128 _maxK = maxK ?? (UInt128)5_000_000UL; + private readonly UInt128 _residueDivisorSetCount = residueDivisorSets ?? (UInt128)PerfectNumberConstants.ExtraDivisorCycleSearchLimit; + private readonly GpuKernelType _kernelType = kernelType; + private readonly bool _useModuloWorkaround = useModuloWorkaround; + private readonly bool _useGpuLucas = useGpuLucas; + private readonly bool _useGpuScan = useGpuScan; // device for pow2mod/incremental scanning + private readonly bool _useGpuOrder = useGpuOrder; // device for order computations + private static readonly ConcurrentDictionary OrderCache = new(); private readonly MersenneNumberIncrementalGpuTester _incrementalGpuTester = new(kernelType, useGpuOrder); private readonly MersenneNumberIncrementalCpuTester _incrementalCpuTester = new(kernelType); private readonly MersenneNumberOrderGpuTester _orderGpuTester = new(kernelType, useGpuOrder); private readonly MersenneNumberOrderCpuTester _orderCpuTester = new(kernelType); private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerGpuTester = new(); private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerCpuTester = new(); - private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null; - private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null; + private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null; + private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null; - // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags. - // Also consider injecting a shared cycles cache (MersenneDivisorCycles.Shared) to both CPU and GPU testers. + // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags. + // Also consider injecting a shared cycles cache (MersenneDivisorCycles.Shared) to both CPU and GPU testers. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static UInt128 DivideRoundUp(UInt128 value, UInt128 divisor) - { - if (divisor == UInt128.Zero) - { - return UInt128.Zero; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static UInt128 DivideRoundUp(UInt128 value, UInt128 divisor) + { + if (divisor == UInt128.Zero) + { + return UInt128.Zero; + } - UInt128 result = value / divisor; - if (result * divisor == value) - { - return result; - } + UInt128 result = value / divisor; + if (result * divisor == value) + { + return result; + } - return result + UInt128.One; - } + return result + UInt128.One; + } - public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL) + public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL) { bool cacheEnabled = useOrderCache; if (!cacheEnabled) @@ -158,168 +158,168 @@ public sealed class MersenneNumberTester( return; } - UInt128[] qs = qsBuffer; + UInt128[] qs = qsBuffer; - var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); - var accelerator = gpuLease.Accelerator; - var orderKernel = gpuLease.OrderKernel!; + var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); + var accelerator = gpuLease.Accelerator; + var orderKernel = gpuLease.OrderKernel!; - // Guard long-running kernels by chunking the warm-up across batches. - // Reuse the same ScanBatchSize knob used for scanning. - int batchSize = GpuConstants.ScanBatchSize; - ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; - int offset = 0; - while (offset < idx) - { - int count = Math.Min(batchSize, idx - offset); - var qBuffer = accelerator.Allocate1D(count); - var orderBuffer = accelerator.Allocate1D(count); - // Convert to device-friendly GpuUInt128 on the fly - var tmp = System.Buffers.ArrayPool.Shared.Rent(count); - for (int i = 0; i < count; i++) - { - tmp[i] = (PerfectNumbers.Core.Gpu.GpuUInt128)qs[offset + i]; - } - // Copy only the populated elements to the device buffer to avoid overrun on pooled arrays. - qBuffer.View.CopyFromCPU(ref tmp[0], count); - System.Buffers.ArrayPool.Shared.Return(tmp); - orderKernel(count, exponent, divMul, qBuffer.View, orderBuffer.View); - accelerator.Synchronize(); + // Guard long-running kernels by chunking the warm-up across batches. + // Reuse the same ScanBatchSize knob used for scanning. + int batchSize = GpuConstants.ScanBatchSize; + ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; + int offset = 0; + while (offset < idx) + { + int count = Math.Min(batchSize, idx - offset); + var qBuffer = accelerator.Allocate1D(count); + var orderBuffer = accelerator.Allocate1D(count); + // Convert to device-friendly GpuUInt128 on the fly + var tmp = System.Buffers.ArrayPool.Shared.Rent(count); + for (int i = 0; i < count; i++) + { + tmp[i] = (PerfectNumbers.Core.Gpu.GpuUInt128)qs[offset + i]; + } + // Copy only the populated elements to the device buffer to avoid overrun on pooled arrays. + qBuffer.View.CopyFromCPU(ref tmp[0], count); + System.Buffers.ArrayPool.Shared.Return(tmp); + orderKernel(count, exponent, divMul, qBuffer.View, orderBuffer.View); + accelerator.Synchronize(); - ulong[] orders = orderBuffer.GetAsArray1D(); - for (int i = 0; i < count; i++) - { - ulong order = orders[i]; - if (order == 0UL) - { - order = qs[offset + i].CalculateOrder(); - } + ulong[] orders = orderBuffer.GetAsArray1D(); + for (int i = 0; i < count; i++) + { + ulong order = orders[i]; + if (order == 0UL) + { + order = qs[offset + i].CalculateOrder(); + } - if (useOrderCache) - { - OrderCache[qs[offset + i]] = order; - } - } + if (useOrderCache) + { + OrderCache[qs[offset + i]] = order; + } + } - offset += count; - orderBuffer.Dispose(); - qBuffer.Dispose(); - } + offset += count; + orderBuffer.Dispose(); + qBuffer.Dispose(); + } - gpuLease.Dispose(); - ArrayPool.Shared.Return(qs); + gpuLease.Dispose(); + ArrayPool.Shared.Return(qs); } - public bool IsMersennePrime(ulong exponent) => IsMersennePrime(exponent, out _); + public bool IsMersennePrime(ulong exponent) => IsMersennePrime(exponent, out _); - public bool IsMersennePrime(ulong exponent, out bool divisorsExhausted) - { - divisorsExhausted = false; + public bool IsMersennePrime(ulong exponent, out bool divisorsExhausted) + { + divisorsExhausted = false; - // Safe early rejections using known orders for tiny primes: - // 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime. - if ((exponent % 3UL) == 0UL && exponent != 3UL) - { - divisorsExhausted = true; - return false; - } + // Safe early rejections using known orders for tiny primes: + // 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime. + if ((exponent % 3UL) == 0UL && exponent != 3UL) + { + divisorsExhausted = true; + return false; + } - if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne()) - { - divisorsExhausted = true; - return false; - } + if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne()) + { + divisorsExhausted = true; + return false; + } - // Skip even exponents: for even p > 2, M_p is composite; here p is large. - if ((exponent & 1UL) == 0UL) - { - divisorsExhausted = true; - return false; - } + // Skip even exponents: for even p > 2, M_p is composite; here p is large. + if ((exponent & 1UL) == 0UL) + { + divisorsExhausted = true; + return false; + } - bool prePrime = true; - UInt128 twoP = (UInt128)exponent << 1; // 2 * p - // UInt128 maxDivisor = new(ulong.MaxValue, ulong.MaxValue); - UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1; - bool lastIsSeven = (exponent & 3UL) == 3UL; + bool prePrime = true; + UInt128 twoP = (UInt128)exponent << 1; // 2 * p + // UInt128 maxDivisor = new(ulong.MaxValue, ulong.MaxValue); + UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1; + bool lastIsSeven = (exponent & 3UL) == 3UL; - // In residue mode the residue scan is the final primality check. - if (_useResidue) - { - if (_maxK < maxK) - { - maxK = _maxK; - } + // In residue mode the residue scan is the final primality check. + if (_useResidue) + { + if (_maxK < maxK) + { + maxK = _maxK; + } - if (_maxK == UInt128.Zero || _residueDivisorSetCount == UInt128.Zero) - { - return prePrime; - } + if (_maxK == UInt128.Zero || _residueDivisorSetCount == UInt128.Zero) + { + return prePrime; + } - UInt128 totalLimit = _maxK; - if (totalLimit > maxK) - { - totalLimit = maxK; - } + UInt128 totalLimit = _maxK; + if (totalLimit > maxK) + { + totalLimit = maxK; + } - if (totalLimit == UInt128.Zero) - { - return true; - } + if (totalLimit == UInt128.Zero) + { + return true; + } - UInt128 requestedSets = _residueDivisorSetCount; - if (_useGpuScan) - { - UInt128 maxUsefulSets = DivideRoundUp(totalLimit, (UInt128)GpuConstants.ScanBatchSize); - if (maxUsefulSets == UInt128.Zero) - { - maxUsefulSets = UInt128.One; - } + UInt128 requestedSets = _residueDivisorSetCount; + if (_useGpuScan) + { + UInt128 maxUsefulSets = DivideRoundUp(totalLimit, (UInt128)GpuConstants.ScanBatchSize); + if (maxUsefulSets == UInt128.Zero) + { + maxUsefulSets = UInt128.One; + } - if (requestedSets == UInt128.Zero || requestedSets > maxUsefulSets) - { - requestedSets = maxUsefulSets; - } - } + if (requestedSets == UInt128.Zero || requestedSets > maxUsefulSets) + { + requestedSets = maxUsefulSets; + } + } - if (requestedSets == UInt128.Zero) - { - requestedSets = UInt128.One; - } + if (requestedSets == UInt128.Zero) + { + requestedSets = UInt128.One; + } - UInt128 perSetLimit = DivideRoundUp(totalLimit, requestedSets); - if (perSetLimit == UInt128.Zero) - { - perSetLimit = UInt128.One; - } + UInt128 perSetLimit = DivideRoundUp(totalLimit, requestedSets); + if (perSetLimit == UInt128.Zero) + { + perSetLimit = UInt128.One; + } - UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit); - UInt128 effectiveSets = requestedSets; - if (setsNeeded < effectiveSets) - { - effectiveSets = setsNeeded; - } + UInt128 setsNeeded = DivideRoundUp(totalLimit, perSetLimit); + UInt128 effectiveSets = requestedSets; + if (setsNeeded < effectiveSets) + { + effectiveSets = setsNeeded; + } - bool scanCompleted = false; + bool scanCompleted = false; - if (_useGpuScan) - { - _residueGpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted); - } - else - { - _residueCpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted); - } + if (_useGpuScan) + { + _residueGpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted); + } + else + { + _residueCpuTester!.Scan(exponent, twoP, lastIsSeven, perSetLimit, effectiveSets, totalLimit, ref prePrime, ref scanCompleted); + } - if (!prePrime) - { - divisorsExhausted = true; - return false; - } + if (!prePrime) + { + divisorsExhausted = true; + return false; + } - divisorsExhausted = scanCompleted; - return true; - } + divisorsExhausted = scanCompleted; + return true; + } if (!_useIncremental) { @@ -337,64 +337,64 @@ public sealed class MersenneNumberTester( _incrementalCpuTester.Scan(exponent, twoPPre, lastIsSevenPre, maxKPre, ref prePrime); } - if (!prePrime) - { - divisorsExhausted = true; - return false; - } + if (!prePrime) + { + divisorsExhausted = true; + return false; + } - bool lucasPrime = _useGpuLucas - ? _lucasLehmerGpuTester.IsPrime(exponent, runOnGpu: true) - : _lucasLehmerCpuTester.IsPrime(exponent); - divisorsExhausted = true; - return lucasPrime; - } + bool lucasPrime = _useGpuLucas + ? _lucasLehmerGpuTester.IsPrime(exponent, runOnGpu: true) + : _lucasLehmerCpuTester.IsPrime(exponent); + divisorsExhausted = true; + return lucasPrime; + } - bool isPrime = true; + bool isPrime = true; - if (useOrder) - { - if (_useGpuOrder) - { - _orderGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); - } - else - { - _orderCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); - } - } - else - { - if (_useGpuScan) - { - _incrementalGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); - } - else - { - _incrementalCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); - } - } + if (useOrder) + { + if (_useGpuOrder) + { + _orderGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); + } + else + { + _orderCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); + } + } + else + { + if (_useGpuScan) + { + _incrementalGpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); + } + else + { + _incrementalCpuTester.Scan(exponent, twoP, lastIsSeven, maxK, ref isPrime); + } + } - divisorsExhausted = true; - return isPrime; - } + divisorsExhausted = true; + return isPrime; + } - // Limit of k to scan in the pre-Lucas prescan - public const int PreLucasPreScanK = 5_000_000; + // Limit of k to scan in the pre-Lucas prescan + public const int PreLucasPreScanK = 5_000_000; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool DividesMersenne(ulong exponent, ulong prime) - { - return 2UL.ModPow64(exponent, prime) == 1UL; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DividesMersenne(ulong exponent, ulong prime) + { + return 2UL.ModPow64(exponent, prime) == 1UL; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView qs, ArrayView orders) - { - UInt128 q = qs[index]; - UInt128 pow = exponent.PowMod(q); - orders[index] = pow == 1UL ? exponent : 0UL; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView qs, ArrayView orders) + { + UInt128 q = qs[index]; + UInt128 pow = exponent.PowMod(q); + orders[index] = pow == 1UL ? exponent : 0UL; + } }