Use thread-static pools in CPU divisor testing
This commit is contained in:
1 parent
a70113a99d
commit
e44662413a
3 files changed
+257
-105
No files matched your search
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
@@ -6,7 +6,9 @@ namespace PerfectNumbers.Core.Cpu;
|
||||
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
private readonly object _sync = new();
|
||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = [];
|
||||
[ThreadStatic]
|
||||
private static DivisorScanSession? _threadLocalSession; // The nested session type stays private, so pool it here.
|
||||
|
||||
private ulong _divisorLimit;
|
||||
private ulong _lastStatusDivisor;
|
||||
private int _batchSize = 1_024;
|
||||
@@ -20,23 +22,23 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
|
||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||
_lastStatusDivisor = 0UL;
|
||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||
_lastStatusDivisor = 0UL;
|
||||
}
|
||||
|
||||
public ulong DivisorLimit
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _divisorLimit;
|
||||
return _divisorLimit;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||
{
|
||||
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||
|
||||
@@ -76,8 +78,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
ulong divisorLimit = _divisorLimit;
|
||||
int length = primes.Length;
|
||||
for (int index = 0; index < length; index++)
|
||||
int length = primes.Length;
|
||||
for (int index = 0; index < length; index++)
|
||||
{
|
||||
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||
}
|
||||
@@ -85,18 +87,49 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||
{
|
||||
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
||||
{
|
||||
session.Reset();
|
||||
return session;
|
||||
}
|
||||
DivisorScanSession? session = _threadLocalSession;
|
||||
if (session is not null)
|
||||
{
|
||||
_threadLocalSession = null;
|
||||
session.Reset();
|
||||
return session;
|
||||
}
|
||||
|
||||
return new DivisorScanSession(this);
|
||||
return new DivisorScanSession(this);
|
||||
}
|
||||
|
||||
private void ReturnSession(DivisorScanSession session)
|
||||
{
|
||||
_sessionPool.Add(session);
|
||||
_threadLocalSession = session;
|
||||
}
|
||||
|
||||
private struct FactorCacheLease
|
||||
{
|
||||
private Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? _cache;
|
||||
|
||||
public void EnsureInitialized(ref Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache)
|
||||
{
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? current = cache;
|
||||
if (current is null)
|
||||
{
|
||||
current = ThreadStaticPools.TakeMersenneFactorCacheDictionary();
|
||||
cache = current;
|
||||
}
|
||||
|
||||
_cache = current;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache = _cache;
|
||||
if (cache is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadStaticPools.ReturnMersenneFactorCacheDictionary(cache);
|
||||
_cache = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckDivisors(
|
||||
@@ -108,10 +141,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
processedCount = 0UL;
|
||||
processedAll = false;
|
||||
|
||||
if (allowedMax < 3UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 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)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
UInt128 step = (UInt128)prime << 1;
|
||||
if (step == UInt128.Zero)
|
||||
@@ -128,8 +163,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
return false;
|
||||
}
|
||||
|
||||
FactorCacheLease factorCacheLease = new FactorCacheLease();
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
|
||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||
bool foundDivisor = false;
|
||||
|
||||
byte step10 = (byte)(step % 10UL);
|
||||
byte step8 = (byte)(step % 8UL);
|
||||
@@ -161,28 +197,22 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
|
||||
ulong divisorCycle;
|
||||
|
||||
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cacheForCycle = null;
|
||||
if (candidate > PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||
factorCacheLease.EnsureInitialized(ref factorCache);
|
||||
cacheForCycle = factorCache;
|
||||
}
|
||||
else
|
||||
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, cacheForCycle, out ulong divisorCycle))
|
||||
{
|
||||
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||
}
|
||||
divisorCycle = 0UL;
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
// A cycle equal to the tested exponent (which is prime in this path)
|
||||
// guarantees that the candidate divides the corresponding Mersenne
|
||||
// number because the order of 2 modulo the divisor is exactly p.
|
||||
processedAll = true;
|
||||
return true;
|
||||
foundDivisor = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
@@ -200,6 +230,15 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
remainder11 = AddMod(remainder11, step11, (byte)11);
|
||||
}
|
||||
|
||||
factorCacheLease.Dispose();
|
||||
factorCache = null; // The pooled dictionary was returned to the thread-static slot.
|
||||
|
||||
if (foundDivisor)
|
||||
{
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
processedAll = divisor > limit;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,13 +17,32 @@ 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;
|
||||
|
||||
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
||||
|
||||
public MersenneDivisorCycles()
|
||||
{
|
||||
}
|
||||
|
||||
private static Dictionary<ulong, int> RentFactorCountDictionary()
|
||||
{
|
||||
return ThreadStaticPools.TakeFactorCountDictionary();
|
||||
}
|
||||
|
||||
private static void ReturnFactorCountDictionary(Dictionary<ulong, int> dictionary)
|
||||
{
|
||||
ThreadStaticPools.ReturnFactorCountDictionary(dictionary);
|
||||
}
|
||||
|
||||
private static Dictionary<ulong, int> RentFactorScratchDictionary()
|
||||
{
|
||||
return ThreadStaticPools.TakeFactorScratchDictionary();
|
||||
}
|
||||
|
||||
private static void ReturnFactorScratchDictionary(Dictionary<ulong, int> dictionary)
|
||||
{
|
||||
ThreadStaticPools.ReturnFactorScratchDictionary(dictionary);
|
||||
}
|
||||
|
||||
public void LoadFrom(string path)
|
||||
{
|
||||
EnsureSmallBuffer();
|
||||
@@ -316,19 +335,27 @@ public class MersenneDivisorCycles
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<ulong, int> factorCounts = new(8);
|
||||
if (twoCount > 0)
|
||||
{
|
||||
factorCounts[2UL] = twoCount;
|
||||
}
|
||||
Dictionary<ulong, int> factorCounts = RentFactorCountDictionary();
|
||||
|
||||
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
||||
try
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (twoCount > 0)
|
||||
{
|
||||
factorCounts[2UL] = twoCount;
|
||||
}
|
||||
|
||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||
return true;
|
||||
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReturnFactorCountDictionary(factorCounts);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AccumulateFactors(
|
||||
@@ -370,37 +397,46 @@ public class MersenneDivisorCycles
|
||||
return true;
|
||||
}
|
||||
|
||||
Dictionary<ulong, int> scratch = new(8);
|
||||
if (!TryFactorIntoCountsInternal(value, scratch))
|
||||
Dictionary<ulong, int> scratch = RentFactorScratchDictionary();
|
||||
|
||||
try
|
||||
{
|
||||
factorization = default!;
|
||||
return false;
|
||||
}
|
||||
if (!TryFactorIntoCountsInternal(value, scratch))
|
||||
{
|
||||
factorization = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
int count = scratch.Count;
|
||||
ulong[] primes = new ulong[count];
|
||||
byte[] exponents = new byte[count];
|
||||
int index = 0;
|
||||
foreach (KeyValuePair<ulong, int> entry in scratch)
|
||||
int count = scratch.Count;
|
||||
ulong[] primes = new ulong[count];
|
||||
byte[] exponents = new byte[count];
|
||||
int index = 0;
|
||||
foreach (KeyValuePair<ulong, int> entry in scratch)
|
||||
{
|
||||
primes[index] = entry.Key;
|
||||
exponents[index] = checked((byte)entry.Value);
|
||||
index++;
|
||||
}
|
||||
|
||||
Array.Sort(primes, exponents);
|
||||
factorization = new FactorCacheEntry(primes, exponents, count);
|
||||
|
||||
if (cache is not null && cacheResult)
|
||||
{
|
||||
cache[value] = factorization;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
primes[index] = entry.Key;
|
||||
exponents[index] = checked((byte)entry.Value);
|
||||
index++;
|
||||
ReturnFactorScratchDictionary(scratch);
|
||||
}
|
||||
|
||||
Array.Sort(primes, exponents);
|
||||
factorization = new FactorCacheEntry(primes, exponents, count);
|
||||
|
||||
if (cache is not null && cacheResult)
|
||||
{
|
||||
cache[value] = factorization;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
||||
{
|
||||
// This guard terminates the recursion once Pollard-Rho factorization has reduced the input to 1.
|
||||
if (value <= 1UL)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1,41 +1,118 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core
|
||||
{
|
||||
public static class ThreadStaticPools
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static ArrayPool<FactorEntry>? _factorEntryPool;
|
||||
public static ArrayPool<FactorEntry> FactorEntryPool
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _factorEntryPool ??= ArrayPool<FactorEntry>.Create();
|
||||
}
|
||||
}
|
||||
[ThreadStatic]
|
||||
private static ArrayPool<FactorEntry>? _factorEntryPool;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ArrayPool<FactorEntry128>? _factorEntry128Pool;
|
||||
public static ArrayPool<FactorEntry128> FactorEntry128Pool
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _factorEntry128Pool ??= ArrayPool<FactorEntry128>.Create();
|
||||
}
|
||||
}
|
||||
public static ArrayPool<FactorEntry> FactorEntryPool
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _factorEntryPool ??= ArrayPool<FactorEntry>.Create();
|
||||
}
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static ArrayPool<ulong>? _ulongPool;
|
||||
public static ArrayPool<ulong> UlongPool
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _ulongPool ??= ArrayPool<ulong>.Create();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[ThreadStatic]
|
||||
private static ArrayPool<FactorEntry128>? _factorEntry128Pool;
|
||||
|
||||
public static ArrayPool<FactorEntry128> FactorEntry128Pool
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _factorEntry128Pool ??= ArrayPool<FactorEntry128>.Create();
|
||||
}
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static ArrayPool<ulong>? _ulongPool;
|
||||
|
||||
public static ArrayPool<ulong> UlongPool
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _ulongPool ??= ArrayPool<ulong>.Create();
|
||||
}
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? _mersenneFactorCacheDictionary;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> TakeMersenneFactorCacheDictionary()
|
||||
{
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache = _mersenneFactorCacheDictionary;
|
||||
if (cache is null)
|
||||
{
|
||||
return new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
||||
}
|
||||
|
||||
_mersenneFactorCacheDictionary = null;
|
||||
cache.Clear();
|
||||
return cache;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReturnMersenneFactorCacheDictionary(Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> cache)
|
||||
{
|
||||
cache.Clear();
|
||||
_mersenneFactorCacheDictionary = cache;
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ulong, int>? _factorCountDictionary;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Dictionary<ulong, int> TakeFactorCountDictionary()
|
||||
{
|
||||
Dictionary<ulong, int>? dictionary = _factorCountDictionary;
|
||||
if (dictionary is null)
|
||||
{
|
||||
return new Dictionary<ulong, int>(8);
|
||||
}
|
||||
|
||||
_factorCountDictionary = null;
|
||||
dictionary.Clear();
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReturnFactorCountDictionary(Dictionary<ulong, int> dictionary)
|
||||
{
|
||||
dictionary.Clear();
|
||||
_factorCountDictionary = dictionary;
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ulong, int>? _factorScratchDictionary;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Dictionary<ulong, int> TakeFactorScratchDictionary()
|
||||
{
|
||||
Dictionary<ulong, int>? dictionary = _factorScratchDictionary;
|
||||
if (dictionary is null)
|
||||
{
|
||||
return new Dictionary<ulong, int>(8);
|
||||
}
|
||||
|
||||
_factorScratchDictionary = null;
|
||||
dictionary.Clear();
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReturnFactorScratchDictionary(Dictionary<ulong, int> dictionary)
|
||||
{
|
||||
dictionary.Clear();
|
||||
_factorScratchDictionary = dictionary;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user