From 752c07c57ebc2b3b03d65389880ca13e5d04f399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Mon, 20 Oct 2025 00:14:36 +0200 Subject: [PATCH] Persist partial factor primality in pending entries --- .../PrimeOrderCalculator.Cpu.cs | 89 ++++++++----------- PerfectNumbers.Core/ThreadStaticPools.cs | 59 ------------ 2 files changed, 36 insertions(+), 112 deletions(-) diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 91078ef..8fac79f 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -10,10 +10,34 @@ namespace PerfectNumbers.Core; internal static partial class PrimeOrderCalculator { - internal readonly struct PendingEntry(ulong value, bool knownComposite) + internal readonly struct PendingEntry { - public readonly ulong Value = value; - public readonly bool KnownComposite = knownComposite; + public readonly ulong Value; + public readonly bool KnownComposite; + public readonly bool HasKnownPrimality; + public readonly bool IsPrime; + + public PendingEntry(ulong value, bool knownComposite) + { + Value = value; + KnownComposite = knownComposite; + HasKnownPrimality = knownComposite; + IsPrime = false; + } + + private PendingEntry(ulong value, bool knownComposite, bool hasKnownPrimality, bool isPrime) + { + Value = value; + KnownComposite = knownComposite; + HasKnownPrimality = hasKnownPrimality; + IsPrime = isPrime; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PendingEntry WithPrimality(bool isPrime) + { + return new PendingEntry(Value, KnownComposite, true, isPrime); + } } private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config) @@ -721,44 +745,6 @@ internal static partial class PrimeOrderCalculator } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool EvaluatePrimalityPartialFactor(ref Dictionary? localCache, ulong candidate) - { - if (TryGetCachedPrimality(candidate, out bool cached)) - { - return cached; - } - - ulong globalCacheLimit = (ulong)PerfectNumberConstants.MaxQForDivisorCycles; - bool bypassesGlobalCache = candidate > globalCacheLimit; - if (bypassesGlobalCache) - { - Dictionary? cache = localCache; - if (cache is not null && cache.TryGetValue(candidate, out bool cachedValue)) - { - return cachedValue; - } - } - - bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(candidate); - CachePrimalityResult(candidate, isPrime); - - if (bypassesGlobalCache) - { - Dictionary? cache = localCache; - if (cache is null) - { - cache = ThreadStaticPools.RentUlongBoolDictionary(4); - cache.Clear(); - localCache = cache; - } - - cache[candidate] = isPrime; - } - - return isPrime; - } - private static PartialFactorResult PartialFactor(ulong value, in PrimeOrderSearchConfig config) { if (value <= 1UL) @@ -795,9 +781,6 @@ internal static partial class PrimeOrderCalculator List pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(2); Stack? compositeStack = null; PartialFactorResult result; - Dictionary? primalityCache = null; - - if (!gpuFactored) { // We don't need to worry about leftovers, because we always use indexes within the calculated counts @@ -862,7 +845,7 @@ internal static partial class PrimeOrderCalculator // continue; // } - bool isPrime = EvaluatePrimalityPartialFactor(ref primalityCache, composite); + bool isPrime = GetOrComputePrimality(composite); if (isPrime) { @@ -920,9 +903,14 @@ internal static partial class PrimeOrderCalculator continue; } - bool isPrime = EvaluatePrimalityPartialFactor(ref primalityCache, composite); + if (!entry.HasKnownPrimality) + { + bool isPrime = GetOrComputePrimality(composite); + entry = entry.WithPrimality(isPrime); + pending[index] = entry; + } - if (isPrime) + if (entry.IsPrime) { AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); } @@ -946,7 +934,7 @@ internal static partial class PrimeOrderCalculator } else { - cofactorIsPrime = EvaluatePrimalityPartialFactor(ref primalityCache, cofactor); + cofactorIsPrime = GetOrComputePrimality(cofactor); } ArrayPool pool = ThreadStaticPools.FactorEntryPool; @@ -1021,11 +1009,6 @@ internal static partial class PrimeOrderCalculator // ThreadStaticPools.IntPool.Return(exponentSlotsArray); - if (primalityCache is not null) - { - ThreadStaticPools.ReturnUlongBoolDictionary(primalityCache); - } - if (counts is not null) { ThreadStaticPools.ReturnUlongIntDictionary(counts); diff --git a/PerfectNumbers.Core/ThreadStaticPools.cs b/PerfectNumbers.Core/ThreadStaticPools.cs index 3fc47e5..33a9de2 100644 --- a/PerfectNumbers.Core/ThreadStaticPools.cs +++ b/PerfectNumbers.Core/ThreadStaticPools.cs @@ -278,65 +278,6 @@ namespace PerfectNumbers.Core pool.Add(dictionary); } - [ThreadStatic] - private static Stack>? _ulongBoolDictionaryPool; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Dictionary RentUlongBoolDictionary(int capacityHint) - { - Stack>? pool = _ulongBoolDictionaryPool; - if (pool is null) - { - pool = new Stack>(4); - _ulongBoolDictionaryPool = pool; - } - - // Keep the previous null-check implementation here for reference. - // Do not uncomment this branch; the pool is always initialized above. - // if (pool is not null && pool.Count > 0) - // { - // Dictionary dictionary = pool.Pop(); - // if (dictionary.Count < capacityHint) - // { - // dictionary.EnsureCapacity(capacityHint); - // } - // - // return dictionary; - // } - - if (pool.Count > 0) - { - Dictionary dictionary = pool.Pop(); - // Callers clear dictionaries after renting to avoid duplicate work. - // if (dictionary.Count > 0) - // { - // dictionary.Clear(); - // } - if (dictionary.Count < capacityHint) - { - dictionary.EnsureCapacity(capacityHint); - } - - return dictionary; - } - - return new Dictionary(capacityHint); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnUlongBoolDictionary(Dictionary dictionary) - { - Stack>? pool = _ulongBoolDictionaryPool; - if (pool is null) - { - pool = new Stack>(4); - _ulongBoolDictionaryPool = pool; - } - - // Do not clear pooled dictionaries here; callers must clear on rent. - pool.Push(dictionary); - } - [ThreadStatic] private static Dictionary? _mersenneFactorCacheDictionary;