From a7fc34e37abdb76b4dc908c51b2c7879528d3598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 26 Oct 2025 16:45:18 +0100 Subject: [PATCH] Inline PrimeTester CPU path and reuse thread pools --- PerfectNumbers.Core/PrimeTester.cs | 91 ++++++++++++++++-------------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index d1c8d2a..4e56414 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -13,7 +13,7 @@ namespace PerfectNumbers.Core; public sealed class PrimeTester { - public PrimeTester(bool useInternal = false) + public PrimeTester() { } @@ -24,7 +24,47 @@ public sealed class PrimeTester public bool IsPrime(ulong n, CancellationToken ct) { - return IsPrimeInternal(n, ct); + // EvenPerfectBitScanner feeds monotonically increasing odd exponents that start well above the + // small-prime cutoffs and already exclude multiples of five. These guards remain for targeted + // tests and diagnostics so ad-hoc callers can still probe small or even values when needed. + bool isTwo = n == 2UL; + bool isOdd = (n & 1UL) != 0UL; + ulong mod5 = n % 5UL; + bool divisibleByFive = n > 5UL && mod5 == 0UL; + + bool result = n >= 2UL && (isTwo || isOdd) && !divisibleByFive; + + // Production scans always fall through to trial division because n is already > 5 by the time this + // path executes. Keep the guard so synthetic callers that hammer tiny inputs can skip the loop. + bool requiresTrialDivision = result && n >= 7UL && !isTwo; + + if (requiresTrialDivision) + { + bool sharesMaxExponentFactor = n.Mod10() == 1UL && SharesFactorWithMaxExponent(n); + result &= !sharesMaxExponentFactor; + + if (result) + { + var smallPrimeDivisorsLength = PrimesGenerator.SmallPrimes.Length; + uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes; + ulong[] smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2; + for (int i = 0; i < smallPrimeDivisorsLength; i++) + { + if (smallPrimeDivisorsMul[i] > n) + { + break; + } + + if (n % smallPrimeDivisors[i] == 0) + { + result = false; + break; + } + } + } + } + + return result; } public static bool IsPrimeGpu(ulong n) @@ -65,41 +105,8 @@ public sealed class PrimeTester public static bool IsPrimeInternal(ulong n, CancellationToken ct) { - bool isTwo = n == 2UL; - bool isOdd = (n & 1UL) != 0UL; - ulong mod5 = n % 5UL; - bool divisibleByFive = n > 5UL && mod5 == 0UL; - - bool result = n >= 2UL && (isTwo || isOdd) && !divisibleByFive; - bool requiresTrialDivision = result && n >= 7UL && !isTwo; - - if (requiresTrialDivision) - { - bool sharesMaxExponentFactor = n.Mod10() == 1UL && SharesFactorWithMaxExponent(n); - result &= !sharesMaxExponentFactor; - - if (result) - { - var smallPrimeDivisorsLength = PrimesGenerator.SmallPrimes.Length; - uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes; - ulong[] smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2; - for (int i = 0; i < smallPrimeDivisorsLength; i++) - { - if (smallPrimeDivisorsMul[i] > n) - { - break; - } - - if (n % smallPrimeDivisors[i] == 0) - { - result = false; - break; - } - } - } - } - - return result; + // Preserve the legacy entry point for callers that bypass the thread-local caches. + return Exclusive.IsPrime(n, ct); } @@ -123,7 +130,8 @@ public sealed class PrimeTester var scratch = state.RentScratch(batchSize, accelerator); var input = scratch.Input; var output = scratch.Output; - ulong[] temp = ArrayPool.Shared.Rent(batchSize); + ArrayPool pool = ThreadStaticPools.UlongPool; + ulong[] temp = pool.Rent(batchSize); try { @@ -145,7 +153,7 @@ public sealed class PrimeTester } finally { - ArrayPool.Shared.Return(temp); + pool.Return(temp, clearArray: false); state.ReturnScratch(scratch); } } @@ -385,13 +393,14 @@ public sealed class PrimeTester var inputBuffer = accelerator.Allocate1D(length); var resultBuffer = accelerator.Allocate1D(length); - ulong[] temp = ArrayPool.Shared.Rent(length); + ArrayPool pool = ThreadStaticPools.UlongPool; + ulong[] temp = pool.Rent(length); values.CopyTo(temp); inputBuffer.View.CopyFromCPU(ref temp[0], length); kernel(length, inputBuffer.View, resultBuffer.View); accelerator.Synchronize(); resultBuffer.View.CopyToCPU(ref results[0], length); - ArrayPool.Shared.Return(temp); + pool.Return(temp, clearArray: false); resultBuffer.Dispose(); inputBuffer.Dispose(); gpu.Dispose();