Pool prime factor partial results for divisor factoring
This commit is contained in:
1 parent
8e7df60dbf
commit
99c98cd017
2 files changed
+315
-46
No files matched your search
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -18,6 +19,12 @@ public class MersenneDivisorCycles
|
|||||||
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
|
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
|
||||||
private ulong[]? _smallCycles;
|
private ulong[]? _smallCycles;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, int>? s_factorCountsPool;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, int>? s_factorScratchPool;
|
||||||
|
|
||||||
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
||||||
|
|
||||||
public MersenneDivisorCycles()
|
public MersenneDivisorCycles()
|
||||||
@@ -330,21 +337,28 @@ public class MersenneDivisorCycles
|
|||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Dictionary<ulong, int> factorCounts = new(8);
|
Dictionary<ulong, int> factorCounts = AcquireFactorCountDictionary(8);
|
||||||
// The divisor candidates are odd, so phi is even and this branch always records at least one factor of two.
|
try
|
||||||
// if (twoCount > 0)
|
|
||||||
// {
|
|
||||||
// factorCounts[2UL] = twoCount;
|
|
||||||
// }
|
|
||||||
factorCounts[2UL] = twoCount;
|
|
||||||
|
|
||||||
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
|
||||||
{
|
{
|
||||||
return false;
|
// The divisor candidates are odd, so phi is even and this branch always records at least one factor of two.
|
||||||
}
|
// if (twoCount > 0)
|
||||||
|
// {
|
||||||
|
// factorCounts[2UL] = twoCount;
|
||||||
|
// }
|
||||||
|
factorCounts[2UL] = twoCount;
|
||||||
|
|
||||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
||||||
return true;
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReleaseFactorCountDictionary(factorCounts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AccumulateFactors(
|
private static bool AccumulateFactors(
|
||||||
@@ -358,61 +372,119 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry factorization))
|
FactorCacheEntry cachedFactorization;
|
||||||
|
PrimeFactorPartialResult? lease = null;
|
||||||
|
bool fromCache;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return false;
|
if (!TryGetFactorization(value, cache, cacheResult, out cachedFactorization, out lease, out fromCache))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromCache)
|
||||||
|
{
|
||||||
|
ulong[] primes = cachedFactorization.Primes;
|
||||||
|
byte[] exponents = cachedFactorization.Exponents;
|
||||||
|
int length = cachedFactorization.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
AddFactor(counts, primes[i], exponents[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lease is null || lease.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<ulong> primesSpan = lease.Primes;
|
||||||
|
ReadOnlySpan<byte> exponentsSpan = lease.Exponents;
|
||||||
|
int spanLength = lease.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < spanLength; i++)
|
||||||
|
{
|
||||||
|
AddFactor(counts, primesSpan[i], exponentsSpan[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
ulong[] primes = factorization.Primes;
|
|
||||||
byte[] exponents = factorization.Exponents;
|
|
||||||
int length = factorization.Count;
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
|
||||||
{
|
{
|
||||||
AddFactor(counts, primes[i], exponents[i]);
|
lease?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGetFactorization(
|
private static bool TryGetFactorization(
|
||||||
ulong value,
|
ulong value,
|
||||||
Dictionary<ulong, FactorCacheEntry>? cache,
|
Dictionary<ulong, FactorCacheEntry>? cache,
|
||||||
bool cacheResult,
|
bool cacheResult,
|
||||||
out FactorCacheEntry factorization)
|
out FactorCacheEntry factorization,
|
||||||
|
out PrimeFactorPartialResult? lease,
|
||||||
|
out bool fromCache)
|
||||||
{
|
{
|
||||||
if (cache is not null && cache.TryGetValue(value, out factorization))
|
factorization = default;
|
||||||
|
lease = null;
|
||||||
|
fromCache = false;
|
||||||
|
|
||||||
|
if (value <= 1UL)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<ulong, int> scratch = new(8);
|
if (cache is not null && cache.TryGetValue(value, out factorization))
|
||||||
if (!TryFactorIntoCountsInternal(value, scratch))
|
|
||||||
{
|
{
|
||||||
factorization = default!;
|
fromCache = true;
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = scratch.Count;
|
Dictionary<ulong, int> scratch = AcquireFactorScratchDictionary(8);
|
||||||
ulong[] primes = new ulong[count];
|
try
|
||||||
byte[] exponents = new byte[count];
|
|
||||||
int index = 0;
|
|
||||||
foreach (KeyValuePair<ulong, int> entry in scratch)
|
|
||||||
{
|
{
|
||||||
primes[index] = entry.Key;
|
if (!TryFactorIntoCountsInternal(value, scratch))
|
||||||
exponents[index] = checked((byte)entry.Value);
|
{
|
||||||
index++;
|
factorization = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = scratch.Count;
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimeFactorPartialResult result = PrimeFactorPartialResult.Rent(count);
|
||||||
|
lease = result;
|
||||||
|
|
||||||
|
Span<ulong> primes = result.PrimeWriteSpan;
|
||||||
|
Span<byte> exponents = result.ExponentWriteSpan;
|
||||||
|
int index = 0;
|
||||||
|
foreach (KeyValuePair<ulong, int> entry in scratch)
|
||||||
|
{
|
||||||
|
primes[index] = entry.Key;
|
||||||
|
exponents[index] = checked((byte)entry.Value);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.CommitCount(index);
|
||||||
|
result.Sort();
|
||||||
|
|
||||||
|
if (cache is not null && cacheResult)
|
||||||
|
{
|
||||||
|
FactorCacheEntry cacheEntry = result.ToCacheEntry();
|
||||||
|
cache[value] = cacheEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
Array.Sort(primes, exponents);
|
|
||||||
factorization = new FactorCacheEntry(primes, exponents, count);
|
|
||||||
|
|
||||||
if (cache is not null && cacheResult)
|
|
||||||
{
|
{
|
||||||
cache[value] = factorization;
|
ReleaseFactorScratchDictionary(scratch);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
||||||
@@ -608,6 +680,53 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Thread-local dictionary pools keep the factoring helpers reusable so the GC stays out of the by-divisor CPU loop.
|
||||||
|
private static Dictionary<ulong, int> AcquireFactorCountDictionary(int capacityHint)
|
||||||
|
{
|
||||||
|
Dictionary<ulong, int>? dictionary = s_factorCountsPool;
|
||||||
|
if (dictionary is null)
|
||||||
|
{
|
||||||
|
return new Dictionary<ulong, int>(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_factorCountsPool = null;
|
||||||
|
dictionary.Clear();
|
||||||
|
dictionary.EnsureCapacity(capacityHint);
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReleaseFactorCountDictionary(Dictionary<ulong, int> dictionary)
|
||||||
|
{
|
||||||
|
dictionary.Clear();
|
||||||
|
if (s_factorCountsPool is null)
|
||||||
|
{
|
||||||
|
s_factorCountsPool = dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<ulong, int> AcquireFactorScratchDictionary(int capacityHint)
|
||||||
|
{
|
||||||
|
Dictionary<ulong, int>? dictionary = s_factorScratchPool;
|
||||||
|
if (dictionary is null)
|
||||||
|
{
|
||||||
|
return new Dictionary<ulong, int>(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_factorScratchPool = null;
|
||||||
|
dictionary.Clear();
|
||||||
|
dictionary.EnsureCapacity(capacityHint);
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReleaseFactorScratchDictionary(Dictionary<ulong, int> dictionary)
|
||||||
|
{
|
||||||
|
dictionary.Clear();
|
||||||
|
if (s_factorScratchPool is null)
|
||||||
|
{
|
||||||
|
s_factorScratchPool = dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static ulong NextRandomUInt64()
|
private static ulong NextRandomUInt64()
|
||||||
{
|
{
|
||||||
Span<byte> buffer = stackalloc byte[8];
|
Span<byte> buffer = stackalloc byte[8];
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
internal sealed class PrimeFactorPartialResult : IDisposable
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static PrimeFactorPartialResult? s_poolHead;
|
||||||
|
|
||||||
|
private PrimeFactorPartialResult? _next;
|
||||||
|
private ulong[]? _primes;
|
||||||
|
private byte[]? _exponents;
|
||||||
|
private bool _arraysFromPool;
|
||||||
|
private int _capacity;
|
||||||
|
|
||||||
|
private PrimeFactorPartialResult()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrimeFactorPartialResult Rent(int capacity)
|
||||||
|
{
|
||||||
|
PrimeFactorPartialResult? instance = s_poolHead;
|
||||||
|
if (instance is null)
|
||||||
|
{
|
||||||
|
instance = new PrimeFactorPartialResult();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_poolHead = instance._next;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance._next = null;
|
||||||
|
instance.EnsureCapacity(capacity);
|
||||||
|
instance.Count = 0;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count { get; private set; }
|
||||||
|
|
||||||
|
internal ReadOnlySpan<ulong> Primes => _primes is null ? ReadOnlySpan<ulong>.Empty : _primes.AsSpan(0, Count);
|
||||||
|
|
||||||
|
internal ReadOnlySpan<byte> Exponents => _exponents is null ? ReadOnlySpan<byte>.Empty : _exponents.AsSpan(0, Count);
|
||||||
|
|
||||||
|
internal Span<ulong> PrimeWriteSpan => _primes is null ? Span<ulong>.Empty : _primes.AsSpan(0, _capacity);
|
||||||
|
|
||||||
|
internal Span<byte> ExponentWriteSpan => _exponents is null ? Span<byte>.Empty : _exponents.AsSpan(0, _capacity);
|
||||||
|
|
||||||
|
internal void CommitCount(int count)
|
||||||
|
{
|
||||||
|
Count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Sort()
|
||||||
|
{
|
||||||
|
if (Count == 0 || _primes is null || _exponents is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Sort(_primes, _exponents, 0, Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal MersenneDivisorCycles.FactorCacheEntry ToCacheEntry()
|
||||||
|
{
|
||||||
|
if (Count == 0 || _primes is null || _exponents is null)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong[] primes = new ulong[Count];
|
||||||
|
byte[] exponents = new byte[Count];
|
||||||
|
Array.Copy(_primes, 0, primes, 0, Count);
|
||||||
|
Array.Copy(_exponents, 0, exponents, 0, Count);
|
||||||
|
return new MersenneDivisorCycles.FactorCacheEntry(primes, exponents, Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_arraysFromPool)
|
||||||
|
{
|
||||||
|
if (_primes is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<ulong>.Shared.Return(_primes, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_exponents is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(_exponents, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_arraysFromPool = false;
|
||||||
|
_primes = null;
|
||||||
|
_exponents = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_capacity = 0;
|
||||||
|
Count = 0;
|
||||||
|
|
||||||
|
_next = s_poolHead;
|
||||||
|
s_poolHead = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int capacity)
|
||||||
|
{
|
||||||
|
if (capacity <= 0)
|
||||||
|
{
|
||||||
|
_capacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_primes is not null && _primes.Length >= capacity)
|
||||||
|
{
|
||||||
|
_capacity = capacity;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_arraysFromPool)
|
||||||
|
{
|
||||||
|
if (_primes is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<ulong>.Shared.Return(_primes, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_exponents is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(_exponents, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_arraysFromPool = false;
|
||||||
|
_primes = null;
|
||||||
|
_exponents = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capacity >= PerfectNumberConstants.PooledArrayThreshold)
|
||||||
|
{
|
||||||
|
_primes = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||||
|
_exponents = ArrayPool<byte>.Shared.Rent(capacity);
|
||||||
|
_arraysFromPool = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_primes = new ulong[capacity];
|
||||||
|
_exponents = new byte[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
_capacity = capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user