Use thread-static pools in CPU divisor testing
This commit is contained in:
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user