Merge pull request #215 from mlemanczyk/codex/analyze-cpu-paths-in-evenperfectbitscanner

Adjust CPU pow2 Montgomery helper for small divisors
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-10-20 18:41:38 +02:00
commit 20f30dc182
20 files changed
+33 -132

No files matched your search

@@ -50,9 +50,6 @@ public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType)
{
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
@@ -62,21 +59,12 @@ public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType)
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)
{
@@ -31,8 +31,7 @@ public class MersenneNumberLucasLehmerCpuTester
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.
// Lucas–Lehmer squares the evolving residue, so we stay on the Montgomery powmod path rather than the pow2-specific helper.
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(m)) - two;
if (s < UInt128.Zero)
{
@@ -35,8 +35,6 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
{
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);
@@ -49,20 +47,13 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
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)
{
@@ -44,9 +44,6 @@ internal struct ExponentRemainderStepper
if (!_hasState || exponent <= _previousExponent)
{
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
// 2× gains for large exponents.
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_previousExponent = exponent;
_hasState = true;
@@ -57,8 +54,6 @@ internal struct ExponentRemainderStepper
// TODO: Once divisor cycle lengths are mandatory, pull the delta multiplier from the
// single-block divisor-cycle snapshot so we can skip the powmod entirely and reuse the
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
_previousExponent = exponent;
@@ -75,8 +70,6 @@ internal struct ExponentRemainderStepper
if (!_hasState || exponent <= _previousExponent)
{
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
// residue checks align with the optimized ProcessEightBitWindows timings.
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_previousExponent = exponent;
_hasState = true;
@@ -86,8 +79,6 @@ internal struct ExponentRemainderStepper
ulong delta = exponent - _previousExponent;
// TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
// TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
// currently lags behind the benchmarked implementation for huge divisor cycles.
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
_previousExponent = exponent;
@@ -121,8 +112,6 @@ internal struct ExponentRemainderStepper
if (!_hasState || exponent <= _previousExponent)
{
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_previousExponent = exponent;
_hasState = true;
+2 -2
View File
@@ -7,8 +7,8 @@ namespace PerfectNumbers.Core.Gpu;
public static class GpuContextPool
{
private static readonly bool PoolingEnabled = true;
// TODO: Introduce accelerator-specific warmup so pooled contexts precompile the ProcessEightBitWindows kernels and load
// divisor-cycle data before the first scan begins.
// The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization
// stays stable; GpuKernelPool JIT-compiles them on first use alongside the small-cycle uploads.
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
public static bool ForceCpu { get; set; } = false;
+2 -12
View File
@@ -1020,18 +1020,8 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong ShiftLeftByNativeChunk(ulong value, ulong modulus)
{
// TODO: Collapse this eight-step shift ladder into the ProcessEightBitWindows helper once it lands so
// we reuse the precomputed window residues instead of emitting `% modulus` after every shift.
value = (value << 1) % modulus;
value = (value << 1) % modulus;
value = (value << 1) % modulus;
value = (value << 1) % modulus;
value = (value << 1) % modulus;
value = (value << 1) % modulus;
value = (value << 1) % modulus;
value = (value << 1) % modulus;
return value;
// Multiply by 2^NativeModuloChunkBits in one pass so the native-modulo path avoids eight `%` reductions.
return MulMod64(value, 1UL << NativeModuloChunkBits, modulus);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -21,8 +21,6 @@ internal static class DivisorKernels
ReadOnlyGpuUInt128 readOnlyMod = divisor;
if (x == 0)
{
// TODO: Swap Pow2Mod for the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
// GpuPow2ModBenchmarks showed the windowed kernel cutting large divisors from ~51 µs to ~21 µs.
baseVal = GpuUInt128.Pow2Mod(exponent, in readOnlyMod);
result[0] = baseVal.High == 0UL && baseVal.Low == 1UL ? (byte)1 : (byte)0;
return;
@@ -40,8 +38,6 @@ internal static class DivisorKernels
GpuUInt128 pow = baseVal;
pow.ModPow(exponent / ux, in readOnlyMod);
// TODO: Replace this trailing Pow2Mod call with the ProcessEightBitWindows variant once the shared
// windowed helper lands so mixed-radix decompositions benefit from the same GPU speedup.
var part2 = GpuUInt128.Pow2Mod(exponent % ux, in readOnlyMod);
GpuUInt128 product = pow;
ReadOnlyGpuUInt128 part2ReadOnly = part2.AsReadOnly();
@@ -96,8 +96,6 @@ internal static class IncrementalKernels
}
ulong phi64 = phi.Low;
// TODO: Replace these Pow2Mod calls with the ProcessEightBitWindows helper when the shared windowed
// scalar implementation lands; benchmarks showed the windowed kernel trimming per-divisor runtime by ~2.4×.
if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One)
{
orders[index] = 0UL;
@@ -194,8 +192,6 @@ internal static class IncrementalKernels
}
ulong phi64 = phi.Low;
// TODO: Upgrade this pow2mod order kernel to the ProcessEightBitWindows helper once available so GPU residue
// scans avoid the single-bit ladder that benchmarks found to be 2.3× slower on large exponents.
if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One)
{
return;
@@ -22,8 +22,6 @@ internal static class OrderKernels
}
ulong phi64 = phi.Low;
// TODO: Once the ProcessEightBitWindows helper is available, switch this order kernel to that faster
// Pow2Mod variant so cycle checks inherit the same gains observed in GpuPow2ModBenchmarks.
GpuUInt128 pow = GpuUInt128.Pow2Mod(phi64, in readOnlyQ);
if (pow != GpuUInt128.One)
{
@@ -77,8 +77,6 @@ internal static class Pow2ModKernels
return;
}
}
// TODO: Swap Pow2Minus1Mod for the eight-bit window helper once the scalar version switches;
// benchmarks show the windowed variant cuts large-divisor scans from ~51 µs to ~21 µs.
if (GpuUInt128.Pow2Minus1Mod(exponent, in readOnlyQ) != GpuUInt128.Zero)
{
orders[index] = 0UL;
@@ -170,8 +168,6 @@ internal static class Pow2ModKernels
return;
}
}
// TODO: Replace this Pow2Mod check with the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
// residue order scans will then benefit from the same 2× speedup the benchmarked windowed kernel delivered.
if (GpuUInt128.Pow2Mod(exponent, in readOnlyQ) != GpuUInt128.One)
{
return;
@@ -112,7 +112,7 @@ public class MersenneNumberLucasLehmerGpuTester
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
var accelerator = gpu.Accelerator;
var kernel = GetBatchKernel(accelerator); // TODO: Switch this batch path to the ProcessEightBitWindows residue kernel once Lucas–Lehmer integrates the benchmarked windowed pow2 helper for small exponents.
var kernel = GetBatchKernel(accelerator);
var expBuffer = accelerator.Allocate1D<ulong>(count);
ulong[] expArray = ArrayPool<ulong>.Shared.Rent(count);
@@ -479,8 +479,6 @@ public class MersenneNumberLucasLehmerGpuTester
{
if ((exponent & 1UL) != 0UL)
{
// TODO: Route these powmods through the ProcessEightBitWindows helper once it lands for GPU
// host-side fallbacks so the Lucas–Lehmer setup reuses the benchmarked windowed ladder.
result = MulMod(result, value, modulus);
}
@@ -22,8 +22,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
{
GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel,
_ => gpuLease.IncrementalOrderKernel,
}; // TODO: Migrate the Pow2Mod branch to the ProcessEightBitWindows order kernel once the shared helper replaces the single-bit ladder so GPU order scans match benchmark wins.
// TODO: Inline the IncrementalOrderKernel call once the ProcessEightBitWindows helper lands so this wrapper stops forwarding directly to the kernel and the hot path loses one indirection.
};
var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
@@ -26,7 +26,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
UInt128 kStart = 1UL;
UInt128 limit = maxK + UInt128.One;
byte last = lastIsSeven ? (byte)1 : (byte)0;
var kernel = gpuLease.Pow2ModKernel; // TODO: Swap this to the ProcessEightBitWindows kernel once GpuUInt128.Pow2Minus1Mod adopts the shared windowed helper measured fastest in GpuPow2ModBenchmarks.
var kernel = gpuLease.Pow2ModKernel;
twoP.Mod10_8_5_3(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
step10 = step10.Mod10();
@@ -985,9 +985,6 @@ public class MersenneDivisorCycles
private static ulong CalculateCycleLengthFallback(ulong divisor)
{
// Otherwise, find order of 2 mod divisor using the shared unrolled-hex stepping sequence.
// TODO: Expose a GPU-first branch here so high divisors leverage the ProcessEightBitWindows
// kernel measured fastest in CycleLengthGpuVsCpuBenchmarks, returning the result without
// storing it in the shared cache.
return divisor.CalculateMersenneDivisorCycleLengthUnrolledHexCpu();
}
@@ -94,8 +94,6 @@ public sealed class MersenneNumberTester(
continue;
}
// TODO: Swap this fallback to the shared windowed order helper once CalculateOrder migrates
// to the ProcessEightBitWindows pipeline so warm-ups stop invoking the slower legacy powmod.
ulong ord = q.CalculateOrder();
OrderCache[q] = ord;
}
@@ -184,8 +182,6 @@ public sealed class MersenneNumberTester(
ulong order = orders[i];
if (order == 0UL)
{
// TODO: Replace this CalculateOrder call with the upcoming windowed helper so
// GPU warm-ups reuse the faster pow2 ladder measured in the CPU order benchmarks.
order = qs[offset + i].CalculateOrder();
}
@@ -323,8 +319,6 @@ public sealed class MersenneNumberTester(
private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView<UInt128> qs, ArrayView<ulong> orders)
{
UInt128 q = qs[index];
// TODO: Swap this legacy kernel over to the ProcessEightBitWindows helper so GPU order scans share the
// eight-bit window pow2 implementation that beat the classic PowMod path in the GpuPow2Mod benchmarks.
UInt128 pow = exponent.PowMod(q);
orders[index] = pow == 1UL ? exponent : 0UL;
}
+15 -23
View File
@@ -17,10 +17,17 @@ public class MersenneResidueStepper
m = modulus;
p = p0;
// TODO: Replace these `%` reductions with the shared ProcessEightBitWindows helper so initialization benefits from the
// windowed pow2mod pipeline proven faster than the generic modulo path.
M_mod = M_mod_m_at_p0 % m;
pow2_mod = (M_mod + 1) % m;
// Inputs are already reduced in production; keep the old modulo guard commented for debugging reference.
// TODO: Confirm whether GPU execution ever constructs this stepper; if so, split the CPU/GPU initialization so
// the accelerator path retains the modulo reductions that prevent divergence.
// M_mod = M_mod_m_at_p0 % m;
// pow2_mod = (M_mod + 1) % m;
M_mod = M_mod_m_at_p0;
pow2_mod = M_mod + 1;
if (pow2_mod >= m)
{
pow2_mod -= m;
}
}
// TODO: Inline this accessor into callers so the hot residue path reads the backing fields directly
@@ -34,28 +41,12 @@ public class MersenneResidueStepper
return State();
}
UInt128 g = UInt128.One;
ulong absDelta = (ulong)Math.Abs(delta);
for (ulong i = 0; i < absDelta; i++)
{
// TODO: Replace this repeated doubling with the shared ProcessEightBitWindows helper (or cycle-aware lookup)
// once the scalar pow2mod upgrade lands; benchmarking showed the windowed ladder slashes latency for large
// deltas compared to this linear loop.
// TODO: Once divisor cycles power the residue stepper, prefer stepping by cycle lengths instead of incrementing
// one bit at a time so large jumps stay on the mandatory cycle-accelerated path.
g <<= 1;
if (g >= m)
{
g -= m;
}
}
UInt128 g = absDelta.PowMod(m);
if (delta > 0)
{
// TODO: Once the scalar windowed pow2 helper lands, reroute this `%` path through it to avoid the slower
// multiply-and-mod sequence measured in the legacy benchmarks.
// Reuse the cached 2^p residue and only multiply by the adaptive pow2 step computed above.
pow2_mod = (pow2_mod * g) % m;
}
else
@@ -63,7 +54,8 @@ public class MersenneResidueStepper
// Modular inverse for negative delta
// TODO: Switch to a cached divisor-cycle remainder instead of computing a fresh modular inverse for every
// backward jump; the divisor cycle benchmarks demonstrated significant savings once shared cycle data was used.
pow2_mod = (pow2_mod * ModInverse(g, m)) % m;
UInt128 inverse = ModInverse(g, m);
pow2_mod = (pow2_mod * inverse) % m;
}
p = (ulong)((long)p + delta);
+3 -5
View File
@@ -203,10 +203,8 @@ public sealed class ModResidueTracker
for (; i < divisors.Count; i++)
{
d = divisors[i];
// TODO: Replace this PowMod128 call with the upcoming eight-bit window helper once the
// ProcessEightBitWindows scalar implementation lands so residue updates benefit from the
// ~2× win recorded in GpuPow2ModBenchmarks for large moduli.
UInt128 powDelta = UInt128Numbers.Two.ModPow(delta, d);
// The pow-delta term now calls the windowed Pow2 Montgomery helper so residue updates reuse the fast ladder.
UInt128 powDelta = delta.Pow2MontgomeryModWindowed(d);
// r' = pow2Delta*r + (pow2Delta - 1) mod d
value = powDelta.MulMod(residues[i], d);
value = value.AddMod(powDelta, d);
@@ -262,7 +260,7 @@ public sealed class ModResidueTracker
// instead of recomputing powmods for every divisor; the divisor-cycle benchmarks showed large wins once the
// cached orders were used across scans, and a miss should trigger the configured device to compute the cycle
// immediately without storing it back or requesting additional cache blocks.
UInt128 pow = UInt128Numbers.Two.ModPow(number, divisor);
UInt128 pow = number.Pow2MontgomeryModWindowed(divisor);
// q divides M_p ⇔ 2^p ≡ 1 (mod q) AND ord_q(2) | p
if (pow != UInt128.One)
-2
View File
@@ -553,8 +553,6 @@ public static class UInt128Extensions
while (order % prime == 0UL)
{
temp = order / prime;
// TODO: Switch this divisor-order powmod to the ProcessEightBitWindows helper so the
// cycle factoring loop benefits from the faster windowed pow2 ladder measured in CPU benchmarks.
if (temp.PowModWithCycle(q, cycle) == one)
{
order = temp;
+7 -6
View File
@@ -16,12 +16,13 @@ public static partial class ULongExtensions
// return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
// }
// There is no point in having extra branch, because in real use-cases it'll barely ever hit.
// Out of all 3_358_944_545 Pow2MontgomeryModWindowedCpu calls, we hit Pow2MontgomeryModSingleBit only 772 times.
// if (exponent <= Pow2WindowFallbackThreshold)
// {
// return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
// }
// Small divisors come from the precomputed <= 4,000,000 snapshot and tiny rotation counts
// appear when cycle reductions trigger. Route both cases through the single-bit ladder so the
// CPU path avoids renting the window tables when a cheaper method is available.
if (exponent <= Pow2WindowFallbackThreshold || modulus <= (ulong)PerfectNumberConstants.MaxQForDivisorCycles)
{
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
}
int bitLength = GetPortableBitLengthCpu(exponent);
int windowSize = GetWindowSizeCpu(bitLength);
-19
View File
@@ -15,7 +15,6 @@ Divisor Cycle Cache & Generation
- PerfectNumbers.Core/MersenneDivisorCycles.cs:794: Migrate this generation path to the shared unrolled-hex calculator so the CPU generator matches the GPU benchmark leader for large divisors.
- PerfectNumbers.Core/MersenneDivisorCycles.cs:869: Swap these modulo checks for the cached Mod3/Mod5/Mod7/Mod11 helpers so range initialization avoids repeated `%` calls before the unrolled-hex pipeline takes over.
- PerfectNumbers.Core/MersenneDivisorCycles.cs:919: Replace this modulo sieve with the cached Mod helpers once the divisor-cycle cache is mandatory so cycle enumeration can drop the slower `%` operations in this hot loop.
- PerfectNumbers.Core/MersenneDivisorCycles.cs:994: Expose a GPU-first branch here so high divisors leverage the ProcessEightBitWindows kernel measured fastest in CycleLengthGpuVsCpuBenchmarks, returning the result without storing it in the shared cache.
- PerfectNumbers.Core/UInt128Extensions.cs:372: If the cache lacks this cycle, immediately schedule the configured device (GPU by default) to compute it on the fly and skip inserting it into the cache so wide-order factoring can still leverage cycle stepping without breaching the memory cap or introducing extra synchronization.
Primality & Order Screening
@@ -40,16 +39,6 @@ Montgomery Primitives
- PerfectNumbers.Core/UInt128Extensions.cs:69: Prototype a UInt128-native Pow2MontgomeryModWindowed path that matches the GPU implementation once a faster modular multiplication helper is available, so we can revisit removing the GPU struct dependency without regressing benchmarks.
Cycle-Aware Modular Arithmetic
- PerfectNumbers.Core/ULongExtensions.cs:900: Port this scalar PowMod fallback to the ProcessEightBitWindows helper so CPU callers get the eight-bit window wins measured against the classic square-and-subtract implementation.
- PerfectNumbers.Core/ULongExtensions.cs:952: Wire this cycle-aware overload into the ProcessEightBitWindows helper so the reduced exponent path inherits the faster windowed pow2 routine highlighted in the Pow2Montgomery benchmarks.
- PerfectNumbers.Core/ULongExtensions.cs:986: Replace this modulo with the cached cycle remainder produced by the divisor-cycle cache so PowModWithCycle avoids repeated `%` work, matching the ProcessEightBitWindows wins captured in Pow2MontgomeryModCycleComputationBenchmarks.
- PerfectNumbers.Core/ULongExtensions.cs:1007: Replace this UInt128-cycle overload with the ProcessEightBitWindows helper so large-exponent CPU scans reuse the faster windowed pow2 ladder instead of the manual rotation loop measured to lag behind in benchmarks.
- PerfectNumbers.Core/ULongExtensions.cs:1039: Swap this modulo with the upcoming UInt128 cycle remainder helper so large-exponent scans reuse cached reductions instead of recomputing `%` for every lookup, as demonstrated in Pow2MontgomeryModCycleComputationBenchmarks.
- PerfectNumbers.Core/ULongExtensions.cs:1065: Migrate this UInt128 exponent overload to ProcessEightBitWindows so the large-cycle reductions drop the slow manual loop that underperforms the windowed pow2 helper in the Pow2 benchmark suite.
- PerfectNumbers.Core/ULongExtensions.cs:1097: Swap this modulo with the shared UInt128 cycle remainder helper once available so CRT powmods reuse cached reductions in the windowed ladder, avoiding the `%` cost highlighted in Pow2MontgomeryModCycleComputationBenchmarks.
- PerfectNumbers.Core/ULongExtensions.cs:1144: Replace this `% modulusCandidate` with the cached residue helper derived from Mod10_8_5_3Benchmarks so CRT composition avoids repeated modulo divisions when combining residues for large divisor sets.
- PerfectNumbers.Core/ULongExtensions.cs:1153: Swap this final `% modulus` with the pooled remainder cache so the CRT result write-back avoids one more division, aligning with the optimizations captured in Mod10_8_5_3Benchmarks.
- PerfectNumbers.Core/UInt128Extensions.cs:388: Switch this divisor-order powmod to the ProcessEightBitWindows helper so the cycle factoring loop benefits from the faster windowed pow2 ladder measured in CPU benchmarks.
Status & Progress Reporting
- PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs:296: 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.
@@ -62,11 +51,3 @@ Cycle Remainder Steppers
- PerfectNumbers.Core/CycleRemainderStepper.cs:96: Expose these fields directly once the residue scanners adopt the single-cycle helper so the hot path can read them without paying for an additional wrapper call per iteration.
Exponent Remainder & Montgomery Windowing
- PerfectNumbers.Core/ExponentRemainderStepper.cs:47: Route these state resets through the ProcessEightBitWindows helper once the scalar Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked 2× gains for large exponents.
- PerfectNumbers.Core/ExponentRemainderStepper.cs:57: Once divisor cycle lengths are mandatory, pull the delta multiplier from the single-block divisor-cycle snapshot so we can skip the powmod entirely and reuse the cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
- PerfectNumbers.Core/ExponentRemainderStepper.cs:60: Replace this per-delta powmod with the upcoming windowed ladder so incremental updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
- PerfectNumbers.Core/ExponentRemainderStepper.cs:78: Switch this reload to the shared windowed pow2 helper once available so CPU residue checks align with the optimized ProcessEightBitWindows timings.
- PerfectNumbers.Core/ExponentRemainderStepper.cs:87: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
- PerfectNumbers.Core/ExponentRemainderStepper.cs:89: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that currently lags behind the benchmarked implementation for huge divisor cycles.
- PerfectNumbers.Core/ExponentRemainderStepper.cs:124: Replace this fallback path with the upcoming ProcessEightBitWindows helper so fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
- PerfectNumbers.Core/ExponentRemainderStepper.cs:133: Once the divisor-cycle cache exposes a direct Montgomery delta, multiply it here instead of relying on the caller-provided delta so incremental scans remain in sync with the snapshot without computing additional cycles or mutating cache state.