Streamline divisor factor pooling for CPU hot path

This commit is contained in:
Marek Lemańczyk committed 2025-10-15 21:50:11 +02:00
1 parent c99aebdf68
commit 64c3721db4
3 files changed
+46 -47

No files matched your search

@@ -184,7 +184,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
prime, prime,
divisorData, divisorData,
cacheForCycle, cacheForCycle,
cacheForCycle is not null,
out ulong computedCycle) || computedCycle == 0UL) out ulong computedCycle) || computedCycle == 0UL)
{ {
divisorCycle = cycleCache.GetCycleLength(candidate); divisorCycle = cycleCache.GetCycleLength(candidate);
+32 -44
View File
@@ -96,7 +96,7 @@ public class MersenneDivisorCycles
factorCache = cache; factorCache = cache;
} }
if (TryCalculateCycleLengthForExponent(divisor, exponent, divisorData, cache, usePooledCache: false, out ulong cycleLength) && cycleLength != 0UL) if (TryCalculateCycleLengthForExponent(divisor, exponent, divisorData, cache, out ulong cycleLength) && cycleLength != 0UL)
{ {
return cycleLength == exponent; return cycleLength == exponent;
} }
@@ -268,7 +268,6 @@ public class MersenneDivisorCycles
ulong exponent, ulong exponent,
in MontgomeryDivisorData divisorData, in MontgomeryDivisorData divisorData,
Dictionary<ulong, FactorCacheEntry>? factorCache, Dictionary<ulong, FactorCacheEntry>? factorCache,
bool usePooledCache,
out ulong cycleLength) out ulong cycleLength)
{ {
cycleLength = 0UL; cycleLength = 0UL;
@@ -328,9 +327,8 @@ public class MersenneDivisorCycles
factorCounts[2UL] = twoCount; factorCounts[2UL] = twoCount;
} }
bool poolExponentFactors = usePooledCache || factorCache is null; bool exponentAccumulated = AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true);
bool exponentAccumulated = AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true, allowPooledStorage: poolExponentFactors); bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCache, factorCounts, cacheResult: false);
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCache, factorCounts, cacheResult: false, allowPooledStorage: true);
if (factorsAccumulated) if (factorsAccumulated)
{ {
@@ -346,31 +344,28 @@ public class MersenneDivisorCycles
ulong value, ulong value,
Dictionary<ulong, FactorCacheEntry>? cache, Dictionary<ulong, FactorCacheEntry>? cache,
Dictionary<ulong, int> counts, Dictionary<ulong, int> counts,
bool cacheResult, bool cacheResult)
bool allowPooledStorage)
{ {
if (value <= 1UL) if (value <= 1UL)
{ {
return true; return true;
} }
bool poolStorage = allowPooledStorage || cache is null || !cacheResult; if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry? factorization, out bool releaseAfterUse) || factorization is null)
if (!TryGetFactorization(value, cache, cacheResult, poolStorage, out FactorCacheEntry? factorization, out bool releaseAfterUse) || factorization is null)
{ {
return false; return false;
} }
FactorCacheEntry actualFactorization = factorization; FactorCacheEntry actualFactorization = factorization;
FactorEntry[]? entries = actualFactorization.Entries; KeyValuePair<ulong, int>[]? pairs = actualFactorization.Pairs;
if (entries is not null) if (pairs is not null)
{ {
int length = actualFactorization.Count; int length = actualFactorization.Count;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
FactorEntry entry = entries[i]; KeyValuePair<ulong, int> pair = pairs[i];
AddFactor(counts, entry.Value, entry.Exponent); AddFactor(counts, pair.Key, pair.Value);
} }
} }
@@ -386,7 +381,6 @@ public class MersenneDivisorCycles
ulong value, ulong value,
Dictionary<ulong, FactorCacheEntry>? cache, Dictionary<ulong, FactorCacheEntry>? cache,
bool cacheResult, bool cacheResult,
bool allowPooledStorage,
out FactorCacheEntry? factorization, out FactorCacheEntry? factorization,
out bool releaseAfterUse) out bool releaseAfterUse)
{ {
@@ -423,24 +417,12 @@ public class MersenneDivisorCycles
return true; return true;
} }
bool fromPool = allowPooledStorage; KeyValuePair<ulong, int>[] pairs = ThreadStaticPools.FactorKeyValuePairPool.Rent(count);
FactorEntry[] entries = fromPool ((ICollection<KeyValuePair<ulong, int>>)scratch).CopyTo(pairs, 0);
? ThreadStaticPools.FactorEntryPool.Rent(count)
: new FactorEntry[count];
Dictionary<ulong, int>.Enumerator enumerator = scratch.GetEnumerator();
int index = 0;
while (enumerator.MoveNext())
{
KeyValuePair<ulong, int> current = enumerator.Current;
entries[index] = new FactorEntry(current.Key, current.Value);
index++;
}
ThreadStaticPools.ReturnFactorScratchDictionary(scratch); ThreadStaticPools.ReturnFactorScratchDictionary(scratch);
Array.Sort(entries, 0, count, FactorEntryValueComparer.Instance); Array.Sort(pairs, 0, count, FactorPairValueComparer.Instance);
entry.Initialize(entries, count, fromPool); entry.Initialize(pairs, count, fromPool: true);
factorization = entry; factorization = entry;
if (cache is not null && cacheResult) if (cache is not null && cacheResult)
@@ -476,6 +458,7 @@ public class MersenneDivisorCycles
break; break;
} }
// TODO: Reuse the remainder-tracking update from the cycle heuristic so repeated divisions disappear from this loop.
while ((remaining % prime) == 0UL) while ((remaining % prime) == 0UL)
{ {
AddFactor(counts, prime); AddFactor(counts, prime);
@@ -488,18 +471,23 @@ public class MersenneDivisorCycles
return true; return true;
} }
// TODO: Benchmark PrimeTester.IsPrimeInternal against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites.
if (PrimeTester.IsPrimeInternal(remaining, CancellationToken.None)) if (PrimeTester.IsPrimeInternal(remaining, CancellationToken.None))
{ {
AddFactor(counts, remaining); AddFactor(counts, remaining);
return true; return true;
} }
// TODO: Evaluate folding the remainder-tracking mechanism into PollardRho64 so the walk leverages previously computed residues.
ulong factor = PollardRho64(remaining); ulong factor = PollardRho64(remaining);
if (factor == 0UL || factor == remaining) if (factor == 0UL || factor == remaining)
{ {
// Pollard-Rho can still stall on rare stubborn composites during test and benchmark runs, so keep the failure guard in place
// and let the caller fall back to the cached cycle calculator.
return false; return false;
} }
// TODO: Investigate computing this quotient via remainder tracking or reciprocal multiplication to avoid the division.
ulong quotient = remaining / factor; ulong quotient = remaining / factor;
// Pollard-Rho never returns factors <= 1 on the EvenPerfectBitScanner path; keep the old guard documented // Pollard-Rho never returns factors <= 1 on the EvenPerfectBitScanner path; keep the old guard documented
// for completeness without branching in the hot path. // for completeness without branching in the hot path.
@@ -674,22 +662,22 @@ public class MersenneDivisorCycles
return BinaryPrimitives.ReadUInt64LittleEndian(buffer); return BinaryPrimitives.ReadUInt64LittleEndian(buffer);
} }
private sealed class FactorEntryValueComparer : IComparer<FactorEntry> private sealed class FactorPairValueComparer : IComparer<KeyValuePair<ulong, int>>
{ {
public static readonly FactorEntryValueComparer Instance = new FactorEntryValueComparer(); public static readonly FactorPairValueComparer Instance = new FactorPairValueComparer();
private FactorEntryValueComparer() private FactorPairValueComparer()
{ {
} }
public int Compare(FactorEntry x, FactorEntry y) public int Compare(KeyValuePair<ulong, int> x, KeyValuePair<ulong, int> y)
{ {
if (x.Value < y.Value) if (x.Key < y.Key)
{ {
return -1; return -1;
} }
if (x.Value > y.Value) if (x.Key > y.Key)
{ {
return 1; return 1;
} }
@@ -701,22 +689,22 @@ public class MersenneDivisorCycles
public sealed class FactorCacheEntry public sealed class FactorCacheEntry
{ {
internal FactorCacheEntry? Next; internal FactorCacheEntry? Next;
internal FactorEntry[]? Entries; internal KeyValuePair<ulong, int>[]? Pairs;
internal int Count; internal int Count;
private bool _fromPool; private bool _fromPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Reset() internal void Reset()
{ {
Entries = null; Pairs = null;
Count = 0; Count = 0;
_fromPool = false; _fromPool = false;
Next = null; Next = null;
} }
internal void Initialize(FactorEntry[]? entries, int count, bool fromPool) internal void Initialize(KeyValuePair<ulong, int>[]? pairs, int count, bool fromPool)
{ {
Entries = entries; Pairs = pairs;
Count = count; Count = count;
_fromPool = fromPool; _fromPool = fromPool;
Next = null; Next = null;
@@ -725,10 +713,10 @@ public class MersenneDivisorCycles
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ReturnToPool() internal void ReturnToPool()
{ {
FactorEntry[]? entries = Entries; KeyValuePair<ulong, int>[]? pairs = Pairs;
if (_fromPool && entries is not null) if (_fromPool && pairs is not null)
{ {
ThreadStaticPools.FactorEntryPool.Return(entries, clearArray: false); ThreadStaticPools.FactorKeyValuePairPool.Return(pairs, clearArray: false);
} }
Reset(); Reset();
+14 -2
View File
@@ -32,6 +32,18 @@ namespace PerfectNumbers.Core
} }
} }
[ThreadStatic]
private static ArrayPool<KeyValuePair<ulong, int>>? _factorKeyValuePairPool;
public static ArrayPool<KeyValuePair<ulong, int>> FactorKeyValuePairPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _factorKeyValuePairPool ??= ArrayPool<KeyValuePair<ulong, int>>.Create();
}
}
[ThreadStatic] [ThreadStatic]
private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession; private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession;
@@ -87,9 +99,9 @@ namespace PerfectNumbers.Core
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnMersenneFactorCacheDictionary(Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> cache) public static void ReturnMersenneFactorCacheDictionary(Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> cache)
{ {
foreach (KeyValuePair<ulong, MersenneDivisorCycles.FactorCacheEntry> entry in cache) for (Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>.Enumerator enumerator = cache.GetEnumerator(); enumerator.MoveNext();)
{ {
entry.Value.ReturnToPool(); enumerator.Current.Value.ReturnToPool();
} }
cache.Clear(); cache.Clear();