From 43e2894661f4329c2f1f4245f2bfe6a36f0d04d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 19 Oct 2025 23:48:07 +0200 Subject: [PATCH] Refine partial factor primality caching --- .../PrimeOrderCalculator.Cpu.cs | 6 ++- PerfectNumbers.Core/ThreadStaticPools.cs | 45 +++++++++++++------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 9f1d5c8..ac490dc 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -730,7 +730,8 @@ internal static partial class PrimeOrderCalculator } ulong globalCacheLimit = (ulong)PerfectNumberConstants.MaxQForDivisorCycles; - if (candidate > globalCacheLimit) + bool bypassesGlobalCache = candidate > globalCacheLimit; + if (bypassesGlobalCache) { Dictionary? cache = localCache; if (cache is not null && cache.TryGetValue(candidate, out bool cachedValue)) @@ -742,12 +743,13 @@ internal static partial class PrimeOrderCalculator bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(candidate); CachePrimalityResult(candidate, isPrime); - if (candidate > globalCacheLimit) + if (bypassesGlobalCache) { Dictionary? cache = localCache; if (cache is null) { cache = ThreadStaticPools.RentUlongBoolDictionary(4); + cache.Clear(); localCache = cache; } diff --git a/PerfectNumbers.Core/ThreadStaticPools.cs b/PerfectNumbers.Core/ThreadStaticPools.cs index 0a35395..3fc47e5 100644 --- a/PerfectNumbers.Core/ThreadStaticPools.cs +++ b/PerfectNumbers.Core/ThreadStaticPools.cs @@ -279,22 +279,39 @@ namespace PerfectNumbers.Core } [ThreadStatic] - private static List>? _ulongBoolDictionaryPool; + private static Stack>? _ulongBoolDictionaryPool; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Dictionary RentUlongBoolDictionary(int capacityHint) { - List>? pool = _ulongBoolDictionaryPool; - if (pool is not null && pool.Count > 0) + Stack>? pool = _ulongBoolDictionaryPool; + if (pool is null) { - int lastIndex = pool.Count - 1; - Dictionary dictionary = pool[lastIndex]; - pool.RemoveAt(lastIndex); - if (dictionary.Count > 0) - { - dictionary.Clear(); - } + 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); @@ -309,15 +326,15 @@ namespace PerfectNumbers.Core [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReturnUlongBoolDictionary(Dictionary dictionary) { - List>? pool = _ulongBoolDictionaryPool; + Stack>? pool = _ulongBoolDictionaryPool; if (pool is null) { - pool = new List>(4); + pool = new Stack>(4); _ulongBoolDictionaryPool = pool; } - dictionary.Clear(); - pool.Add(dictionary); + // Do not clear pooled dictionaries here; callers must clear on rent. + pool.Push(dictionary); } [ThreadStatic]