6.5 KiB
CPU by-divisor cache reuse analysis
Scope
The EvenPerfectBitScanner switches to the by-divisor implementation when the CLI selects --mersenne=bydivisor. In CPU-only runs it instantiates MersenneNumberDivisorByDivisorCpuTester and feeds it the primes loaded from the filter file together with any previously recorded outcomes. 【F:EvenPerfectBitScanner/Program.cs†L216-L305】
Each candidate exponent p is a prime at least 138,000,000, the divisors tested for that exponent are generated as q = 2·k·p + 1 with strictly increasing integer k ≥ 1, and both the prime and divisor streams are monotonically increasing with no repetitions. The small-cycle snapshot bundled with the binaries only covers divisors up to four million. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L179-L214】【F:PerfectNumbers.Core/PerfectNumberConstants.cs†L3-L16】
Caches touched in this path
Previous results skip map
When the results file already exists, the loader records every stored p in a dictionary so the new run can skip those primes entirely. 【F:EvenPerfectBitScanner/Program.cs†L286-L305】
- Observed reuse: Entries are only revisited if the user reruns the scan on the same results file. Within a single execution the filter contains each prime at most once, so the dictionary does not experience repeated lookups for identical keys beyond that replay scenario.
Montgomery divisor data creation
Every admissible divisor constructs its Montgomery reduction constants directly before evaluating the cycle length, both in the per-divisor loop and when the reusable scan session validates cached state. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L188-L218】【F:PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs†L27-L69】【F:PerfectNumbers.Core/MontgomeryDivisorData.cs†L32-L45】
- Observed reuse: The helper still runs for every divisor, but because
q = 2·k·p + 1never repeats, no value-based caching is retained. The direct construction simply reflects that reusing previously computed constants provided no benefit on this path.
Divisor cycle cache
If the dedicated cycle solver produces a result, it is returned immediately. Otherwise the code falls back to DivisorCycleCache.GetCycleLength with skipPrimeOrderHeuristic=true. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L190-L218】 This cache stores a preloaded snapshot for divisors up to four million and computes larger cycles on demand. 【F:PerfectNumbers.Core/DivisorCycleCache.cs†L32-L80】
- Observed reuse: The tester only queries the snapshot for divisors at or below four million; larger values invoke
MersenneDivisorCycles.CalculateCycleLengthdirectly. The by-divisor workload never emits small divisors, so the cache is effectively idle and every large divisor triggers a fresh computation without persisting the result.
Factorization cache
MersenneNumberDivisorByDivisorCpuTester attaches a per-prime FactorCacheLease before scanning its divisors. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L152-L225】 The cycle calculator then factors φ(q) = q − 1 by splitting out the power of two, caching the factorization of the current prime exponent, and factoring the residual k without caching. 【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L306-L437】
- Exponent entries: The first divisor processed for a prime forces a full factorization of
p, including a primality check. The resulting(p → {p})entry stays in the dictionary and is reused for every later divisor tied to the same prime, eliminating repeated primality work on subsequent iterations. kvalues: Each divisor introduces a freshk. Those factorisations setcacheResult:false, so the results are discarded immediately after use. Even when the Pollard–Rho recursion discovers small shared factors (for example,kdivisible by three), the cache never sees those values again and therefore captures no reuse for the smaller composites. 【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L329-L437】- Lifetime: Once the tester leaves the loop,
FactorCacheLease.Disposereturns the dictionary to the thread-static pool after releasing every stored factorization entry. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L251-L259】【F:PerfectNumbers.Core/Cpu/FactorCacheLease.cs†L3-L29】 The next prime obtains a cleared dictionary, so cached data never crosses prime boundaries.
Factorization helper pools
The factorization helpers rely on thread-static pools for temporary dictionaries, reusable factor entries, and key-value buffers. These pools only recycle storage; they deliberately clear their contents before handing objects to the next caller. 【F:PerfectNumbers.Core/ThreadStaticPools.cs†L281-L335】
- Observed reuse: The pools successfully avoid new allocations, but they do not provide value-based caching. All number-theoretic results are recomputed for every divisor except the cached exponent entry described above.
Primality probe cache
PrimeOrderCalculator only retains primality results for inputs at or below the four-million snapshot threshold, matching the strict small-factor limit. Larger values—including the monotonically increasing p and q candidates—skip the dictionary entirely, so the cache only serves repeatedly observed small factors uncovered while factoring k. 【F:PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs†L676-L704】
- Observed reuse: The bounded cache keeps quick answers for recurring small primes such as
3or5, while unique large composites are evaluated once and discarded. This prevents the cache from growing with one-off exponent or divisor candidates and aligns its scope with the actual reuse opportunities.
Summary
Only the cached prime factorization of the current exponent and the bounded ≤4,000,000 primality dictionary deliver meaningful reuse on the CPU by-divisor path. All other caches either hold a single most-recent value, serve small divisors that never appear in this workload, or are limited to memory pooling without retaining computed results. The fallback that bypasses the prime-order solver still funnels every large divisor into a fresh cycle computation, so no additional reuse occurs there. Every branch of the factorization workflow—including the direct prime-order success case, the fallback that invokes DivisorCycleCache, and the recursive Pollard–Rho reducer—was reviewed in code; no remaining cache-related areas of this path require inspection.