Streamline divisor factor pooling for CPU hot path
This commit is contained in:
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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in new issue
Block a user