Eliminate GC pressure from divisor cycle factoring

This commit is contained in:
Marek Lemańczyk committed 2025-10-15 19:32:53 +02:00
1 parent c8f8132db7
commit 34fcd62c23
3 files changed
+111 -25

No files matched your search

@@ -199,7 +199,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
cacheForCycle = factorCache;
}
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, cacheForCycle, out ulong divisorCycle))
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, cacheForCycle, cacheForCycle is not null, out ulong divisorCycle))
{
divisorCycle = 0UL;
}
+105 -24
View File
@@ -264,11 +264,28 @@ public class MersenneDivisorCycles
}
public static bool TryCalculateCycleLengthForExponent(
ulong divisor,
ulong divisor,
ulong exponent,
in MontgomeryDivisorData divisorData,
Dictionary<ulong, FactorCacheEntry>? factorCache,
out ulong cycleLength)
{
return TryCalculateCycleLengthForExponent(
divisor,
exponent,
divisorData,
factorCache,
usePooledCache: false,
out cycleLength);
}
public static bool TryCalculateCycleLengthForExponent(
ulong divisor,
ulong exponent,
in MontgomeryDivisorData divisorData,
Dictionary<ulong, FactorCacheEntry>? factorCache,
bool usePooledCache,
out ulong cycleLength)
{
cycleLength = 0UL;
@@ -316,7 +333,9 @@ public class MersenneDivisorCycles
factorCounts[2UL] = twoCount;
}
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
bool poolExponentFactors = usePooledCache || factorCache is null;
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true, poolExponentFactors) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false, allowPooledStorage: true))
{
return false;
}
@@ -334,25 +353,36 @@ public class MersenneDivisorCycles
ulong value,
Dictionary<ulong, FactorCacheEntry>? cache,
Dictionary<ulong, int> counts,
bool cacheResult)
bool cacheResult,
bool allowPooledStorage)
{
if (value <= 1UL)
{
return true;
}
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry factorization))
bool poolStorage = allowPooledStorage || cache is null || !cacheResult;
if (!TryGetFactorization(value, cache, cacheResult, poolStorage, out FactorCacheEntry factorization))
{
return false;
}
ulong[] primes = factorization.Primes;
byte[] exponents = factorization.Exponents;
int length = factorization.Count;
for (int i = 0; i < length; i++)
FactorEntry[]? entries = factorization.Entries;
if (entries is not null)
{
AddFactor(counts, primes[i], exponents[i]);
int length = factorization.Count;
for (int i = 0; i < length; i++)
{
FactorEntry entry = entries[i];
AddFactor(counts, entry.Value, entry.Exponent);
}
}
if (cache is null || !cacheResult)
{
factorization.ReturnToPool();
}
return true;
@@ -362,6 +392,7 @@ public class MersenneDivisorCycles
ulong value,
Dictionary<ulong, FactorCacheEntry>? cache,
bool cacheResult,
bool allowPooledStorage,
out FactorCacheEntry factorization)
{
if (cache is not null && cache.TryGetValue(value, out factorization))
@@ -380,18 +411,26 @@ public class MersenneDivisorCycles
}
int count = scratch.Count;
ulong[] primes = new ulong[count];
byte[] exponents = new byte[count];
if (count == 0)
{
factorization = default!;
return true;
}
bool fromPool = allowPooledStorage;
FactorEntry[] entries = fromPool
? ThreadStaticPools.FactorEntryPool.Rent(count)
: new FactorEntry[count];
int index = 0;
foreach (KeyValuePair<ulong, int> entry in scratch)
{
primes[index] = entry.Key;
exponents[index] = checked((byte)entry.Value);
entries[index] = new FactorEntry(entry.Key, entry.Value);
index++;
}
Array.Sort(primes, exponents);
factorization = new FactorCacheEntry(primes, exponents, count);
Array.Sort(entries, 0, count, FactorEntryValueComparer.Instance);
factorization = new FactorCacheEntry(entries, count, fromPool);
if (cache is not null && cacheResult)
{
@@ -612,19 +651,61 @@ public class MersenneDivisorCycles
return BinaryPrimitives.ReadUInt64LittleEndian(buffer);
}
private sealed class FactorEntryValueComparer : IComparer<FactorEntry>
{
public static readonly FactorEntryValueComparer Instance = new FactorEntryValueComparer();
private FactorEntryValueComparer()
{
}
public int Compare(FactorEntry x, FactorEntry y)
{
if (x.Value < y.Value)
{
return -1;
}
if (x.Value > y.Value)
{
return 1;
}
return 0;
}
}
public readonly struct FactorCacheEntry
{
public FactorCacheEntry(ulong[] primes, byte[] exponents, int count)
{
Primes = primes;
Exponents = exponents;
Count = count;
}
public ulong[] Primes { get; }
private readonly bool _fromPool;
public byte[] Exponents { get; }
internal FactorCacheEntry(FactorEntry[] entries, int count, bool fromPool)
{
Entries = entries;
Count = count;
_fromPool = fromPool;
}
internal FactorEntry[]? Entries { get; }
public int Count { get; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ReturnToPool()
{
if (!_fromPool)
{
return;
}
FactorEntry[]? entries = Entries;
if (entries is null)
{
return;
}
ThreadStaticPools.FactorEntryPool.Return(entries, clearArray: false);
}
}
public static (long nextPosition, long completeCount) FindLast(string path)
+5
View File
@@ -63,6 +63,11 @@ namespace PerfectNumbers.Core
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnMersenneFactorCacheDictionary(Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> cache)
{
foreach (KeyValuePair<ulong, MersenneDivisorCycles.FactorCacheEntry> entry in cache)
{
entry.Value.ReturnToPool();
}
cache.Clear();
_mersenneFactorCacheDictionary = cache;
}