Add diagnostics for removed factorization cache
This commit is contained in:
1 parent
28ff9292b8
commit
1a4818c794
3 files changed
+95
-9
No files matched your search
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -11,6 +12,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
private ulong _lastStatusDivisor;
|
||||
private int _batchSize = 1_024;
|
||||
|
||||
private const int FactorCachePrimeTrackingLimit = 500_000;
|
||||
private static readonly HashSet<ulong> FactorCacheTrackedPrimes = new();
|
||||
private static readonly object FactorCachePrimeLock = new();
|
||||
private static ulong FactorCachePrimeHitCount;
|
||||
private static bool FactorCachePrimeTrackingDisabled;
|
||||
|
||||
[ThreadStatic]
|
||||
private static GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
||||
|
||||
@@ -115,6 +122,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
processedCount = 0UL;
|
||||
processedAll = false;
|
||||
|
||||
ObserveFactorCachePrime(prime);
|
||||
|
||||
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
||||
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
||||
// if (allowedMax < 3UL)
|
||||
@@ -254,6 +263,43 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ObserveFactorCachePrime(ulong prime)
|
||||
{
|
||||
bool logHit = false;
|
||||
ulong hitNumber = 0UL;
|
||||
bool trackingDisabledNow = false;
|
||||
|
||||
lock (FactorCachePrimeLock)
|
||||
{
|
||||
if (FactorCachePrimeTrackingDisabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FactorCacheTrackedPrimes.Add(prime))
|
||||
{
|
||||
FactorCachePrimeHitCount++;
|
||||
hitNumber = FactorCachePrimeHitCount;
|
||||
logHit = true;
|
||||
}
|
||||
else if (FactorCacheTrackedPrimes.Count >= FactorCachePrimeTrackingLimit)
|
||||
{
|
||||
FactorCachePrimeTrackingDisabled = true;
|
||||
FactorCacheTrackedPrimes.Clear();
|
||||
trackingDisabledNow = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (logHit)
|
||||
{
|
||||
Console.WriteLine($"Factor cache hit for exponent {prime} ({hitNumber})");
|
||||
}
|
||||
else if (trackingDisabledNow)
|
||||
{
|
||||
Console.WriteLine("Factor cache hit tracking disabled after exceeding the tracking limit.");
|
||||
}
|
||||
}
|
||||
|
||||
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
|
||||
|
||||
@@ -15,6 +15,12 @@ public class MersenneDivisorCycles
|
||||
private List<(ulong divisor, ulong cycleLength)> _table = [];
|
||||
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
|
||||
private ulong[]? _smallCycles;
|
||||
private const int FactorCacheExponentTrackingLimit = 500_000;
|
||||
private static readonly HashSet<ulong> FactorCacheTrackedExponents = new();
|
||||
private static readonly object FactorCacheTrackingLock = new();
|
||||
private static ulong FactorCacheHitCount;
|
||||
private static bool FactorCacheTrackingDisabled;
|
||||
|
||||
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
||||
|
||||
public MersenneDivisorCycles()
|
||||
@@ -312,6 +318,7 @@ public class MersenneDivisorCycles
|
||||
factorCounts[2UL] = twoCount;
|
||||
}
|
||||
|
||||
ObserveFactorCacheExponent(exponent);
|
||||
bool exponentAccumulated = AccumulateFactors(exponent, factorCounts);
|
||||
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCounts);
|
||||
|
||||
@@ -326,6 +333,43 @@ public class MersenneDivisorCycles
|
||||
return success;
|
||||
}
|
||||
|
||||
private static void ObserveFactorCacheExponent(ulong exponent)
|
||||
{
|
||||
bool logHit = false;
|
||||
ulong hitNumber = 0UL;
|
||||
bool trackingDisabledNow = false;
|
||||
|
||||
lock (FactorCacheTrackingLock)
|
||||
{
|
||||
if (FactorCacheTrackingDisabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FactorCacheTrackedExponents.Add(exponent))
|
||||
{
|
||||
FactorCacheHitCount++;
|
||||
hitNumber = FactorCacheHitCount;
|
||||
logHit = true;
|
||||
}
|
||||
else if (FactorCacheTrackedExponents.Count >= FactorCacheExponentTrackingLimit)
|
||||
{
|
||||
FactorCacheTrackingDisabled = true;
|
||||
FactorCacheTrackedExponents.Clear();
|
||||
trackingDisabledNow = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (logHit)
|
||||
{
|
||||
Console.WriteLine($"Factor cache hit for exponent {exponent} ({hitNumber})");
|
||||
}
|
||||
else if (trackingDisabledNow)
|
||||
{
|
||||
Console.WriteLine("Factor cache hit tracking disabled after exceeding the tracking limit.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AccumulateFactors(
|
||||
ulong value,
|
||||
Dictionary<ulong, int> counts)
|
||||
|
||||
@@ -23,20 +23,16 @@ If the dedicated cycle solver produces a result, it is returned immediately. Oth
|
||||
* **Observed reuse:** The tester only queries the snapshot for divisors at or below four million; larger values invoke `MersenneDivisorCycles.CalculateCycleLength` directly. 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
|
||||
Previous revisions attached a per-prime factorization cache, but profiling showed virtually no reuse and the added bookkeeping slowed the scan. The implementation now recomputes every factorization on demand and documents why the cache should not return. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L152-L221】【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L308-L351】
|
||||
Previous revisions attached a per-prime factorization cache, but profiling showed virtually no reuse and the added bookkeeping slowed the scan. The implementation now recomputes every factorization on demand and documents why the cache should not return. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L101-L264】【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L305-L399】
|
||||
|
||||
* **Current behavior:** Each divisor recomputes the factorization for the exponent and `k` values using thread-static scratch dictionaries only. Inline comments note that the retired cache produced zero hits in production runs. 【F:PerfectNumbers.Core/MersenneDivisorByDivisorCpuTester.cs†L150-L204】【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L332-L347】
|
||||
* **Rationale:** The divisor stream never repeats values during a scan, so caching results provided no benefit while increasing synchronization and pooling overhead. The comments exist to prevent reintroducing the pointless cache. 【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L336-L343】
|
||||
* **Current behavior:** Each divisor recomputes the factorization for the exponent and `k` values using thread-static scratch dictionaries only. Inline comments note that the retired cache produced zero hits in production runs. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L101-L264】【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L321-L399】
|
||||
* **Instrumentation:** `MersenneNumberDivisorByDivisorCpuTester` and `MersenneDivisorCycles` keep static tracking sets that log to the console whenever a repeated exponent appears, incrementing a shared hit counter. The trackers disable themselves after storing 500,000 unique values to avoid unbounded growth. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L15-L125】【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L266-L301】【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L18-L39】【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L321-L369】
|
||||
* **Rationale:** The divisor stream never repeats values during a scan, so caching results provided no benefit while increasing synchronization and pooling overhead. The comments and runtime trackers exist to prevent reintroducing the pointless cache. 【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L321-L399】
|
||||
|
||||
### 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†L270-L316】
|
||||
|
||||
* **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 `3` or `5`, 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
|
||||
With the factorization cache removed, the bounded ≤4,000,000 primality dictionary remains the only 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.
|
||||
With the factorization cache removed, the by-divisor workflow now recomputes every factorization and relies on the small divisor-cycle snapshot plus pooling helpers for reuse. Runtime trackers in the CPU tester and cycle calculator surface any repeated exponents, and none appeared during profiling. 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. 【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L15-L125】【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L266-L301】【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L18-L39】【F:PerfectNumbers.Core/MersenneDivisorCycles.cs†L321-L369】
|
||||
Reference in new issue
Block a user