From 627d2e9b141fef98cd2dc1d98e9cfff2e97fe665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 19 Oct 2025 22:49:38 +0200 Subject: [PATCH] Cache composite primality results in PartialFactor --- .../PrimeOrderCalculator.Cpu.cs | 32 ++++++++++++-- PerfectNumbers.Core/ThreadStaticPools.cs | 42 +++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 536eebd..835b80d 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -757,6 +757,27 @@ internal static partial class PrimeOrderCalculator Stack? compositeStack = null; PartialFactorResult result; + Dictionary? localPrimalityCache = null; + + bool EvaluatePrimality(ulong candidate) + { + Dictionary? cache = localPrimalityCache; + if (cache is not null && cache.TryGetValue(candidate, out bool cachedValue)) + { + return cachedValue; + } + + bool computed = GetOrComputePrimality(candidate); + if (!computed) + { + cache ??= ThreadStaticPools.RentUlongBoolDictionary(4); + cache[candidate] = false; + localPrimalityCache = cache; + } + + return computed; + } + if (!gpuFactored) { // We don't need to worry about leftovers, because we always use indexes within the calculated counts @@ -821,7 +842,7 @@ internal static partial class PrimeOrderCalculator // continue; // } - bool isPrime = GetOrComputePrimality(composite); + bool isPrime = EvaluatePrimality(composite); if (isPrime) { @@ -868,7 +889,7 @@ internal static partial class PrimeOrderCalculator continue; } - bool isPrime = GetOrComputePrimality(composite); + bool isPrime = EvaluatePrimality(composite); if (isPrime) { @@ -894,7 +915,7 @@ internal static partial class PrimeOrderCalculator } else { - cofactorIsPrime = GetOrComputePrimality(cofactor); + cofactorIsPrime = EvaluatePrimality(cofactor); } ArrayPool pool = ThreadStaticPools.FactorEntryPool; @@ -968,6 +989,11 @@ internal static partial class PrimeOrderCalculator // ThreadStaticPools.UlongPool.Return(primeSlotsArray); // ThreadStaticPools.IntPool.Return(exponentSlotsArray); + if (localPrimalityCache is not null) + { + ThreadStaticPools.ReturnUlongBoolDictionary(localPrimalityCache); + } + if (counts is not null) { ThreadStaticPools.ReturnUlongIntDictionary(counts); diff --git a/PerfectNumbers.Core/ThreadStaticPools.cs b/PerfectNumbers.Core/ThreadStaticPools.cs index 33a9de2..0a35395 100644 --- a/PerfectNumbers.Core/ThreadStaticPools.cs +++ b/PerfectNumbers.Core/ThreadStaticPools.cs @@ -278,6 +278,48 @@ namespace PerfectNumbers.Core pool.Add(dictionary); } + [ThreadStatic] + private static List>? _ulongBoolDictionaryPool; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary RentUlongBoolDictionary(int capacityHint) + { + List>? pool = _ulongBoolDictionaryPool; + if (pool is not null && pool.Count > 0) + { + int lastIndex = pool.Count - 1; + Dictionary dictionary = pool[lastIndex]; + pool.RemoveAt(lastIndex); + 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) + { + List>? pool = _ulongBoolDictionaryPool; + if (pool is null) + { + pool = new List>(4); + _ulongBoolDictionaryPool = pool; + } + + dictionary.Clear(); + pool.Add(dictionary); + } + [ThreadStatic] private static Dictionary? _mersenneFactorCacheDictionary;