Add TODOs for divisor metadata caching
This commit is contained in:
1 parent
513fc7acb0
commit
36d316d19c
53 files changed
+929
-393
No files matched your search
@@ -18,7 +18,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
public bool UseDivisorCycles
|
||||
{
|
||||
get => _useDivisorCycles;
|
||||
set => _useDivisorCycles = value;
|
||||
set => _useDivisorCycles = value; // TODO: Delete the toggle once divisor cycle data is always consulted so CPU scans never fall back to the slower pure-Montgomery path.
|
||||
}
|
||||
|
||||
public int BatchSize
|
||||
@@ -191,6 +191,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
lastProcessed = divisor;
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
// TODO: Hoist MontgomeryDivisorData acquisition into the divisor-cycle cache so we reuse the
|
||||
// staged ProcessEightBitWindows-ready metadata instead of reloading the slower Montgomery
|
||||
// structs for every divisor.
|
||||
byte hit;
|
||||
ulong divisorCycle = 0UL;
|
||||
DivisorCycleCache.CycleBlock? cycleBlock = null;
|
||||
@@ -202,6 +205,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Force this path to hydrate divisor cycles from disk once the cache guarantees
|
||||
// coverage so we can retire the slower Montgomery-only fallback highlighted by the CPU
|
||||
// divisor benchmarks.
|
||||
hit = CheckDivisor(prime, false, 0UL, divisorData);
|
||||
}
|
||||
|
||||
@@ -225,6 +231,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
}
|
||||
|
||||
divisor += 2UL;
|
||||
// TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||
// CPU by-divisor benchmarks so we advance directly to the next cached divisor candidate
|
||||
// instead of testing every odd integer.
|
||||
}
|
||||
|
||||
if (!processedAll)
|
||||
@@ -244,7 +253,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
|
||||
ulong residue = useCycles && divisorCycle != 0UL
|
||||
? prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData)
|
||||
: prime.Pow2MontgomeryMod(divisorData);
|
||||
: prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||
// scalar Pow2Minus1Mod adopts it so CPU scans match the GPU speedups.
|
||||
|
||||
return residue == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
@@ -264,6 +274,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
}
|
||||
|
||||
ulong total = _lastStatusDivisor + processedCount;
|
||||
// TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
||||
// status benchmarks so we avoid `%` in this hot loop while still wrapping progress correctly.
|
||||
_lastStatusDivisor = total % interval;
|
||||
}
|
||||
|
||||
@@ -372,6 +384,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||
// keeps the slower Montgomery stepping alive even though the benchmarks showed the cached cycle path is far
|
||||
// faster for large divisor sets.
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
hits[i] = exponentStepper.ComputeNextIsUnity(primes[i]) ? (byte)1 : (byte)0;
|
||||
|
||||
@@ -39,29 +39,41 @@ public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType)
|
||||
|
||||
if (shouldCheck)
|
||||
{
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
reject = exponent.PowModWithCycle(q, qCycle) == UInt128.One;
|
||||
}
|
||||
else
|
||||
{
|
||||
phi = q - one;
|
||||
reject = false;
|
||||
if (phi <= ulong.MaxValue)
|
||||
{
|
||||
phi64 = (ulong)phi;
|
||||
if (phi64.PowModWithCycle(q, qCycle) == one)
|
||||
{
|
||||
halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - one;
|
||||
if (halfPow.BinaryGcd(q) == one)
|
||||
{
|
||||
div = phi64.FastDiv64(exponent, divMul);
|
||||
divPow = div.PowModWithCycle(q, qCycle) - one;
|
||||
if (divPow.BinaryGcd(q) == one)
|
||||
{
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
// TODO: Swap this PowModWithCycle call for the ProcessEightBitWindows helper once
|
||||
// the scalar implementation lands so CPU pow2mod scans match the GPU speedups
|
||||
// captured in GpuPow2ModBenchmarks.
|
||||
reject = exponent.PowModWithCycle(q, qCycle) == UInt128.One;
|
||||
}
|
||||
else
|
||||
{
|
||||
phi = q - one;
|
||||
reject = false;
|
||||
if (phi <= ulong.MaxValue)
|
||||
{
|
||||
phi64 = (ulong)phi;
|
||||
// TODO: Route the phi-based powmods through the upcoming windowed helper to
|
||||
// avoid the current square-and-multiply cost highlighted in the Pow2 bench
|
||||
// suite when scanning large divisors.
|
||||
if (phi64.PowModWithCycle(q, qCycle) == one)
|
||||
{
|
||||
// TODO: Once the windowed pow2 helper is available expose a version
|
||||
// that reuses cached windows here instead of recomputing via the slow
|
||||
// bit ladder for every divisor.
|
||||
halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - one;
|
||||
if (halfPow.BinaryGcd(q) == one)
|
||||
{
|
||||
div = phi64.FastDiv64(exponent, divMul);
|
||||
// TODO: Replace this divisor powmod with the shared windowed
|
||||
// implementation so each candidate uses the benchmarked fast
|
||||
// path instead of the current per-bit multiply chain.
|
||||
divPow = div.PowModWithCycle(q, qCycle) - one;
|
||||
if (divPow.BinaryGcd(q) == one)
|
||||
{
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,39 +3,43 @@ namespace PerfectNumbers.Core;
|
||||
public class MersenneNumberLucasLehmerCpuTester
|
||||
{
|
||||
public bool IsPrime(ulong exponent)
|
||||
{
|
||||
{
|
||||
// Early rejections aligned with incremental/order sieves, but safe for small p:
|
||||
// - If 3 | p and p != 3, then 7 | M_p -> composite.
|
||||
// - If p ≡ 1 (mod 4) and p shares a factor with (p-1), reject fast.
|
||||
// - If p is divisible by divisors specific to numbers ending with 1 or 7, reject fast.
|
||||
// TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering
|
||||
// shares the benchmarked bitmask implementations and avoids slow modulo instructions in the hot path.
|
||||
if (
|
||||
((exponent % 3UL) == 0UL && exponent != 3UL) ||
|
||||
((exponent % 5UL) == 0UL && exponent != 5UL) ||
|
||||
((exponent % 7UL) == 0UL && exponent != 7UL) ||
|
||||
((exponent % 11UL) == 0UL && exponent != 11UL)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt128 s = UInt128Numbers.Four, two = UInt128Numbers.Two,
|
||||
m = (UInt128.One << (int)exponent) - UInt128.One;
|
||||
UInt128 s = UInt128Numbers.Four, two = UInt128Numbers.Two,
|
||||
m = (UInt128.One << (int)exponent) - UInt128.One;
|
||||
|
||||
UInt128 limit = exponent - UInt128Numbers.Two;
|
||||
for (UInt128 i = UInt128.Zero; i < limit; i++)
|
||||
{
|
||||
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(m)) - two;
|
||||
if (s < UInt128.Zero)
|
||||
{
|
||||
s += m;
|
||||
}
|
||||
}
|
||||
UInt128 limit = exponent - UInt128Numbers.Two;
|
||||
for (UInt128 i = UInt128.Zero; i < limit; i++)
|
||||
{
|
||||
// TODO: Port this Lucas–Lehmer powmod to the ProcessEightBitWindows helper so the CPU
|
||||
// fallback benefits from the same windowed ladder that halves runtime in the Pow2 benchmarks.
|
||||
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(m)) - two;
|
||||
if (s < UInt128.Zero)
|
||||
{
|
||||
s += m;
|
||||
}
|
||||
}
|
||||
|
||||
return s == UInt128.Zero;
|
||||
}
|
||||
return s == UInt128.Zero;
|
||||
}
|
||||
}
|
||||
@@ -30,33 +30,42 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
|
||||
}
|
||||
if (shouldCheck)
|
||||
{
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
if (exponent.PowModWithCycle(q, qCycle) == 1UL)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt128 phi = q - 1UL;
|
||||
if (phi <= ulong.MaxValue)
|
||||
{
|
||||
ulong phi64 = (ulong)phi;
|
||||
if (phi64.PowModWithCycle(q, qCycle) == 1UL)
|
||||
{
|
||||
UInt128 halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - 1UL;
|
||||
if (halfPow.BinaryGcd(q) == 1UL)
|
||||
{
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
|
||||
ulong div = phi64.FastDiv64(exponent, divMul);
|
||||
UInt128 divPow = div.PowModWithCycle(q, qCycle) - 1UL;
|
||||
if (divPow.BinaryGcd(q) == 1UL)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
// TODO: Point this CPU pow2mod check at the ProcessEightBitWindows helper once it
|
||||
// ships so by-divisor scans avoid the slow single-bit ladder that benchmarks flagged.
|
||||
if (exponent.PowModWithCycle(q, qCycle) == 1UL)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt128 phi = q - 1UL;
|
||||
if (phi <= ulong.MaxValue)
|
||||
{
|
||||
ulong phi64 = (ulong)phi;
|
||||
// TODO: Switch these phi-based powmods to the shared windowed helper so CPU
|
||||
// fallback paths keep pace with the optimized GPU kernels on large divisors.
|
||||
if (phi64.PowModWithCycle(q, qCycle) == 1UL)
|
||||
{
|
||||
// TODO: Reuse the windowed pow2 helper for halfPow as soon as it is
|
||||
// available instead of recalculating via square-and-multiply.
|
||||
UInt128 halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - 1UL;
|
||||
if (halfPow.BinaryGcd(q) == 1UL)
|
||||
{
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
|
||||
ulong div = phi64.FastDiv64(exponent, divMul);
|
||||
// TODO: Replace this divisor powmod with the ProcessEightBitWindows
|
||||
// implementation so order scans stop paying the slower bit-serial
|
||||
// loop measured in the MulMod benchmarks.
|
||||
UInt128 divPow = div.PowModWithCycle(q, qCycle) - 1UL;
|
||||
if (divPow.BinaryGcd(q) == 1UL)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user