Use thread-static pools in CPU divisor testing

This commit is contained in:
Marek Lemańczyk committed 2025-10-15 18:10:39 +02:00
1 parent a70113a99d
commit e44662413a
3 files changed
+180 -28

No files matched your search

@@ -1,4 +1,4 @@
using System.Collections.Concurrent; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core.Cpu; namespace PerfectNumbers.Core.Cpu;
@@ -6,7 +6,9 @@ namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
{ {
private readonly object _sync = new(); 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 _divisorLimit;
private ulong _lastStatusDivisor; private ulong _lastStatusDivisor;
private int _batchSize = 1_024; private int _batchSize = 1_024;
@@ -85,8 +87,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
{ {
if (_sessionPool.TryTake(out DivisorScanSession? session)) DivisorScanSession? session = _threadLocalSession;
if (session is not null)
{ {
_threadLocalSession = null;
session.Reset(); session.Reset();
return session; return session;
} }
@@ -96,7 +100,36 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private void ReturnSession(DivisorScanSession session) 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( private bool CheckDivisors(
@@ -108,10 +141,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
processedCount = 0UL; processedCount = 0UL;
processedAll = false; processedAll = false;
if (allowedMax < 3UL) // 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.
return false; // if (allowedMax < 3UL)
} // {
// return false;
// }
UInt128 step = (UInt128)prime << 1; UInt128 step = (UInt128)prime << 1;
if (step == UInt128.Zero) if (step == UInt128.Zero)
@@ -128,8 +163,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return false; return false;
} }
FactorCacheLease factorCacheLease = new FactorCacheLease();
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null; Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
DivisorCycleCache cycleCache = DivisorCycleCache.Shared; bool foundDivisor = false;
byte step10 = (byte)(step % 10UL); byte step10 = (byte)(step % 10UL);
byte step8 = (byte)(step % 8UL); 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) if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0)
{ {
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate); MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
ulong divisorCycle; Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cacheForCycle = null;
if (candidate > PerfectNumberConstants.MaxQForDivisorCycles)
{
factorCacheLease.EnsureInitialized(ref factorCache);
cacheForCycle = factorCache;
}
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles) if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, cacheForCycle, out ulong divisorCycle))
{ {
divisorCycle = cycleCache.GetCycleLength(candidate); divisorCycle = 0UL;
}
else
{
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
{
divisorCycle = cycleCache.GetCycleLength(candidate);
}
} }
if (divisorCycle == prime) if (divisorCycle == prime)
{ {
// A cycle equal to the tested exponent (which is prime in this path) foundDivisor = true;
// guarantees that the candidate divides the corresponding Mersenne break;
// number because the order of 2 modulo the divisor is exactly p.
processedAll = true;
return true;
} }
if (divisorCycle == 0UL) if (divisorCycle == 0UL)
@@ -200,6 +230,15 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
remainder11 = AddMod(remainder11, step11, (byte)11); 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; processedAll = divisor > limit;
return false; return false;
} }
+39 -3
View File
@@ -17,13 +17,32 @@ public class MersenneDivisorCycles
private List<(ulong divisor, ulong cycleLength)> _table = []; private List<(ulong divisor, ulong cycleLength)> _table = [];
// 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;
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles(); public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
public 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) public void LoadFrom(string path)
{ {
EnsureSmallBuffer(); EnsureSmallBuffer();
@@ -316,7 +335,10 @@ public class MersenneDivisorCycles
return false; return false;
} }
Dictionary<ulong, int> factorCounts = new(8); Dictionary<ulong, int> factorCounts = RentFactorCountDictionary();
try
{
if (twoCount > 0) if (twoCount > 0)
{ {
factorCounts[2UL] = twoCount; factorCounts[2UL] = twoCount;
@@ -330,6 +352,11 @@ public class MersenneDivisorCycles
cycleLength = ReduceOrder(divisorData, phi, factorCounts); cycleLength = ReduceOrder(divisorData, phi, factorCounts);
return true; return true;
} }
finally
{
ReturnFactorCountDictionary(factorCounts);
}
}
private static bool AccumulateFactors( private static bool AccumulateFactors(
ulong value, ulong value,
@@ -370,7 +397,10 @@ public class MersenneDivisorCycles
return true; return true;
} }
Dictionary<ulong, int> scratch = new(8); Dictionary<ulong, int> scratch = RentFactorScratchDictionary();
try
{
if (!TryFactorIntoCountsInternal(value, scratch)) if (!TryFactorIntoCountsInternal(value, scratch))
{ {
factorization = default!; factorization = default!;
@@ -398,9 +428,15 @@ public class MersenneDivisorCycles
return true; return true;
} }
finally
{
ReturnFactorScratchDictionary(scratch);
}
}
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts) 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) if (value <= 1UL)
{ {
return true; return true;
+77
View File
@@ -1,4 +1,6 @@
using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core namespace PerfectNumbers.Core
@@ -7,6 +9,7 @@ namespace PerfectNumbers.Core
{ {
[ThreadStatic] [ThreadStatic]
private static ArrayPool<FactorEntry>? _factorEntryPool; private static ArrayPool<FactorEntry>? _factorEntryPool;
public static ArrayPool<FactorEntry> FactorEntryPool public static ArrayPool<FactorEntry> FactorEntryPool
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -18,6 +21,7 @@ namespace PerfectNumbers.Core
[ThreadStatic] [ThreadStatic]
private static ArrayPool<FactorEntry128>? _factorEntry128Pool; private static ArrayPool<FactorEntry128>? _factorEntry128Pool;
public static ArrayPool<FactorEntry128> FactorEntry128Pool public static ArrayPool<FactorEntry128> FactorEntry128Pool
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -29,6 +33,7 @@ namespace PerfectNumbers.Core
[ThreadStatic] [ThreadStatic]
private static ArrayPool<ulong>? _ulongPool; private static ArrayPool<ulong>? _ulongPool;
public static ArrayPool<ulong> UlongPool public static ArrayPool<ulong> UlongPool
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -37,5 +42,77 @@ namespace PerfectNumbers.Core
return _ulongPool ??= ArrayPool<ulong>.Create(); 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;
}
} }
} }