From 8d9b8b18298df809ca93407bff2e0c69c46751c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 16 Oct 2025 21:51:42 +0200 Subject: [PATCH 1/2] Replace primality cache with lock-backed dictionary --- PerfectNumbers.Core/PrimeOrderCalculator.cs | 103 +++++++++++++++----- 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index 68fe8a9..6493d6d 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -5,8 +5,8 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using PerfectNumbers.Core.Gpu; -using System.Collections.Concurrent; namespace PerfectNumbers.Core; @@ -787,14 +787,77 @@ internal static partial class PrimeOrderCalculator } // [ThreadStatic] - private static ConcurrentDictionary? _primalityCache = []; + private static Dictionary? _primalityCache; + private static readonly ReaderWriterLockSlim _primalityCacheLock = new(LockRecursionPolicy.NoRecursion); // [ThreadStatic] // private static UInt128 _isPrimeHits; + private static bool TryGetCachedPrimality(ulong value, out bool isPrime) + { + Dictionary? cache = Volatile.Read(ref _primalityCache); + if (cache is null) + { + isPrime = default; + return false; + } + + return cache.TryGetValue(value, out isPrime); + } + + private static void CachePrimalityResult(ulong value, bool isPrime) + { + Dictionary? cache = Volatile.Read(ref _primalityCache); + if (cache is not null && cache.TryGetValue(value, out bool existing) && existing == isPrime) + { + return; + } + + _primalityCacheLock.EnterWriteLock(); + try + { + Dictionary? current = _primalityCache; + if (current is null) + { + Dictionary newCache = new() + { + [value] = isPrime, + }; + Volatile.Write(ref _primalityCache, newCache); + return; + } + + if (current.TryGetValue(value, out bool existingValue) && existingValue == isPrime) + { + return; + } + + Dictionary updated = new(current) + { + [value] = isPrime, + }; + Volatile.Write(ref _primalityCache, updated); + } + finally + { + _primalityCacheLock.ExitWriteLock(); + } + } + + private static bool GetOrComputePrimality(ulong value) + { + if (TryGetCachedPrimality(value, out bool isPrime)) + { + return isPrime; + } + + isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(value); + CachePrimalityResult(value, isPrime); + return isPrime; + } + private static PartialFactorResult PartialFactor(ulong value, in PrimeOrderSearchConfig config) { - var primalityCache = _primalityCache;// ??= []; if (value <= 1UL) { return PartialFactorResult.Empty; @@ -865,17 +928,8 @@ internal static partial class PrimeOrderCalculator // continue; // } - if (!primalityCache!.TryGetValue(composite, out bool isPrime)) - { - // Console.WriteLine($"Checking composite {composite}"); - isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); - _ = primalityCache.TryAdd(composite, isPrime); - } - // else - // { - // Console.WriteLine($"isPrimeHits = {++_isPrimeHits}"); - // } - + bool isPrime = GetOrComputePrimality(composite); + if (isPrime) { AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); @@ -906,12 +960,8 @@ internal static partial class PrimeOrderCalculator for (int i = 0; i < pendingCount; i++) { ulong composite = pending[i]; - if (!primalityCache!.TryGetValue(composite, out bool isPrime)) - { - // Console.WriteLine($"Checking composite {composite}"); - isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); - _ = primalityCache.TryAdd(composite, isPrime); - } + bool isPrime = GetOrComputePrimality(composite); + if (isPrime) { AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); @@ -1166,14 +1216,19 @@ internal static partial class PrimeOrderCalculator continue; } - // TODO: Can we calculate this using residue stepping? We can try reusing modulo calculation for division result or the other way around + // Use DivRem to reuse a single division for the quotient and remainder. int exponent = 0; - do + while (true) { - remainingLocal /= primeCandidate; + ulong quotient = Math.DivRem(remainingLocal, primeCandidate, out ulong divisionRemainder); + if (divisionRemainder != 0UL) + { + break; + } + + remainingLocal = quotient; exponent++; } - while ((remainingLocal % primeCandidate) == 0UL); if (factorCount >= capacity) { From d2e7e4905a281fac50a77ad7e879bb1fef4bb919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 16 Oct 2025 21:52:42 +0200 Subject: [PATCH 2/2] Streamline primality cache locking --- PerfectNumbers.Core/PrimeOrderCalculator.cs | 49 ++++++++------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index 6493d6d..1f205cd 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -795,48 +795,37 @@ internal static partial class PrimeOrderCalculator private static bool TryGetCachedPrimality(ulong value, out bool isPrime) { - Dictionary? cache = Volatile.Read(ref _primalityCache); - if (cache is null) + _primalityCacheLock.EnterReadLock(); + try { - isPrime = default; - return false; - } + Dictionary? cache = _primalityCache; + if (cache is null) + { + isPrime = default; + return false; + } - return cache.TryGetValue(value, out isPrime); + return cache.TryGetValue(value, out isPrime); + } + finally + { + _primalityCacheLock.ExitReadLock(); + } } private static void CachePrimalityResult(ulong value, bool isPrime) { - Dictionary? cache = Volatile.Read(ref _primalityCache); - if (cache is not null && cache.TryGetValue(value, out bool existing) && existing == isPrime) - { - return; - } - _primalityCacheLock.EnterWriteLock(); try { - Dictionary? current = _primalityCache; - if (current is null) + Dictionary? cache = _primalityCache; + if (cache is null) { - Dictionary newCache = new() - { - [value] = isPrime, - }; - Volatile.Write(ref _primalityCache, newCache); - return; + cache = new Dictionary(); + _primalityCache = cache; } - if (current.TryGetValue(value, out bool existingValue) && existingValue == isPrime) - { - return; - } - - Dictionary updated = new(current) - { - [value] = isPrime, - }; - Volatile.Write(ref _primalityCache, updated); + cache[value] = isPrime; } finally {