Merge pull request #163 from mlemanczyk/codex/analyze-caching-strategy-for-mersenne-path

Clarify per-divisor Montgomery recomputation
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-10-13 03:43:36 +02:00
commit 7ef0906a9b
2 files changed
+21 -43

No files matched your search

@@ -253,10 +253,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null; Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
DivisorCycleCache cycleCache = DivisorCycleCache.Shared; DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
ulong cachedModulus = 0UL;
MontgomeryDivisorData cachedDivisorData = default;
bool hasCachedDivisorData = false;
byte step10 = (byte)(step % 10UL); byte step10 = (byte)(step % 10UL);
byte step8 = (byte)(step % 8UL); byte step8 = (byte)(step % 8UL);
byte step5 = (byte)(step % 5UL); byte step5 = (byte)(step % 5UL);
@@ -294,14 +290,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0) if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0)
{ {
if (!hasCachedDivisorData || cachedModulus != candidate) // Each divisor appears only once in this monotonically increasing sequence, so
{ // rebuilding the Montgomery data per candidate avoids caching entries that we
cachedDivisorData = MontgomeryDivisorData.FromModulus(candidate); // would never hit again.
cachedModulus = candidate; MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
hasCachedDivisorData = true;
}
MontgomeryDivisorData divisorData = cachedDivisorData;
ulong divisorCycle; ulong divisorCycle;
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles) if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
@@ -410,10 +402,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null; Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
ulong cachedModulus = 0UL;
MontgomeryDivisorData cachedDivisorData = default;
bool hasCachedDivisorData = false;
const int RemainderTableSize = 6; const int RemainderTableSize = 6;
byte[] remainderBuffer = ArrayPool<byte>.Shared.Rent(RemainderTableSize); byte[] remainderBuffer = ArrayPool<byte>.Shared.Rent(RemainderTableSize);
byte[] stepBuffer = ArrayPool<byte>.Shared.Rent(RemainderTableSize); byte[] stepBuffer = ArrayPool<byte>.Shared.Rent(RemainderTableSize);
@@ -483,6 +471,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (montgomeryBuilder != null && montgomeryBuffer != null) if (montgomeryBuilder != null && montgomeryBuffer != null)
{ {
montgomerySpan = montgomeryBuffer.AsSpan(0, chunkCount); montgomerySpan = montgomeryBuffer.AsSpan(0, chunkCount);
// Every candidate divisor is unique across the scan, so build Montgomery data
// for the current batch and drop it immediately instead of maintaining a cache.
montgomeryBuilder.Build(candidateSpan, montgomerySpan); montgomeryBuilder.Build(candidateSpan, montgomerySpan);
} }
@@ -511,19 +501,16 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
divisorData = montgomerySpan[i]; divisorData = montgomerySpan[i];
if (divisorData.Modulus != candidate) if (divisorData.Modulus != candidate)
{ {
// The GPU builder might skip entries when masks zero them out; recompute on the CPU
// without caching because divisors never repeat within a session.
divisorData = MontgomeryDivisorData.FromModulus(candidate); divisorData = MontgomeryDivisorData.FromModulus(candidate);
} }
} }
else else
{ {
if (!hasCachedDivisorData || cachedModulus != candidate) // Candidates remain unique even when GPU Montgomery support is disabled, so rebuild
{ // the divisor data inline instead of persisting unused cache entries.
cachedDivisorData = MontgomeryDivisorData.FromModulus(candidate); divisorData = MontgomeryDivisorData.FromModulus(candidate);
cachedModulus = candidate;
hasCachedDivisorData = true;
}
divisorData = cachedDivisorData;
} }
ulong divisorCycle; ulong divisorCycle;
@@ -663,9 +650,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{ {
private readonly MersenneNumberDivisorByDivisorCpuTester _owner; private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
private bool _disposed; private bool _disposed;
private bool _hasCachedDivisorData;
private ulong _cachedDivisor;
private MontgomeryDivisorData _cachedDivisorData;
private ulong[]? _primeDeltas; private ulong[]? _primeDeltas;
private ulong[]? _cycleRemainders; private ulong[]? _cycleRemainders;
private ulong[]? _montgomeryDeltas; private ulong[]? _montgomeryDeltas;
@@ -680,9 +664,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
internal void Reset() internal void Reset()
{ {
_disposed = false; _disposed = false;
_hasCachedDivisorData = false;
_cachedDivisor = 0UL;
_cachedDivisorData = default;
} }
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits) public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
@@ -698,20 +679,15 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return; return;
} }
MontgomeryDivisorData cachedData = divisorData; MontgomeryDivisorData effectiveDivisorData = divisorData;
if (cachedData.Modulus != divisor) if (effectiveDivisorData.Modulus != divisor)
{ {
if (!_hasCachedDivisorData || _cachedDivisor != divisor) // Each divisor surfaces only once across the monotonic scan, so rebuild the Montgomery data
{ // instead of retaining a cache entry that would never be reused within future checks.
_cachedDivisorData = MontgomeryDivisorData.FromModulus(divisor); effectiveDivisorData = MontgomeryDivisorData.FromModulus(divisor);
_cachedDivisor = divisor;
_hasCachedDivisorData = true;
} }
cachedData = _cachedDivisorData; ulong modulus = effectiveDivisorData.Modulus;
}
ulong modulus = cachedData.Modulus;
if (modulus <= 1UL || (modulus & 1UL) == 0UL) if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{ {
hits.Clear(); hits.Clear();
@@ -730,11 +706,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (_owner._deltasDevice == ByDivisorDeltasDevice.Cpu) if (_owner._deltasDevice == ByDivisorDeltasDevice.Cpu)
{ {
CheckDivisorCpu(cachedData, divisorCycle, primes, hits); CheckDivisorCpu(effectiveDivisorData, divisorCycle, primes, hits);
return; return;
} }
CheckDivisorGpu(cachedData, divisorCycle, primes, hits, length); CheckDivisorGpu(effectiveDivisorData, divisorCycle, primes, hits, length);
} }
private static void CheckDivisorCpu( private static void CheckDivisorCpu(
@@ -412,6 +412,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong divisorValue = filteredDivisorsSpan[i]; ulong divisorValue = filteredDivisorsSpan[i];
divisorSpan[i] = divisorValue; divisorSpan[i] = divisorValue;
exponentSpan[i] = prime; exponentSpan[i] = prime;
// Divisors never repeat while scanning the monotonically increasing sequence, so regenerate
// the Montgomery data for each entry rather than storing cache lines that no future batch would hit.
divisorDataSpan[i] = MontgomeryDivisorData.FromModulus(divisorValue); divisorDataSpan[i] = MontgomeryDivisorData.FromModulus(divisorValue);
} }