diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index cc825d5..ee77260 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -322,7 +322,7 @@ public class MersenneDivisorCycles factorCounts[2UL] = twoCount; } - if (!AccumulateFactors(exponent, factorCache, factorCounts) || !AccumulateFactors(k, factorCache, factorCounts)) + if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false)) { return false; } @@ -334,14 +334,15 @@ public class MersenneDivisorCycles private static bool AccumulateFactors( ulong value, Dictionary? cache, - Dictionary counts) + Dictionary counts, + bool cacheResult) { if (value <= 1UL) { return true; } - if (!TryGetFactorization(value, cache, out FactorCacheEntry factorization)) + if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry factorization)) { return false; } @@ -361,6 +362,7 @@ public class MersenneDivisorCycles private static bool TryGetFactorization( ulong value, Dictionary? cache, + bool cacheResult, out FactorCacheEntry factorization) { if (cache is not null && cache.TryGetValue(value, out factorization)) @@ -389,7 +391,7 @@ public class MersenneDivisorCycles Array.Sort(primes, exponents); factorization = new FactorCacheEntry(primes, exponents, count); - if (cache is not null) + if (cache is not null && cacheResult) { cache[value] = factorization; } diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index a5fbe57..92ced2b 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; using Open.Numeric.Primes; +using System.Runtime.InteropServices; namespace PerfectNumbers.Core; @@ -27,29 +28,97 @@ public static class MersenneNumberDivisorByDivisorTester _ = threadCount; + candidates.Sort(); + Span candidateSpan = CollectionsMarshal.AsSpan(candidates); + int candidateCount = candidateSpan.Length; + bool applyStartPrime = startPrime > 0UL; int skippedByPreviousResults = 0; - List primesToTest = new(candidates.Count); + + if (previousResults is not null && previousResults.Count > 0) + { + int recordedCount = previousResults.Count; + ulong[] recordedCandidates = ArrayPool.Shared.Rent(recordedCount); + try + { + Span recordedSpan = recordedCandidates.AsSpan(0, recordedCount); + int recordedIndex = 0; + foreach (ulong value in previousResults.Keys) + { + recordedSpan[recordedIndex++] = value; + } + + recordedSpan.Sort(); + + int writeIndex = 0; + int skipIndex = 0; + + for (int readIndex = 0; readIndex < candidateCount; readIndex++) + { + ulong candidate = candidateSpan[readIndex]; + + while (skipIndex < recordedCount && recordedSpan[skipIndex] < candidate) + { + skipIndex++; + } + + if (skipIndex < recordedCount && recordedSpan[skipIndex] == candidate) + { + skippedByPreviousResults++; + skipIndex++; + continue; + } + + candidateSpan[writeIndex++] = candidate; + } + + if (writeIndex < candidateCount) + { + candidates.RemoveRange(writeIndex, candidateCount - writeIndex); + candidateSpan = CollectionsMarshal.AsSpan(candidates); + candidateCount = candidateSpan.Length; + } + } + finally + { + ArrayPool.Shared.Return(recordedCandidates, clearArray: true); + } + } + + if (skippedByPreviousResults > 0) + { + Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results."); + } + + int startIndex = 0; + if (applyStartPrime) + { + startIndex = candidates.BinarySearch(startPrime); + if (startIndex < 0) + { + startIndex = ~startIndex; + } + else + { + while (startIndex > 0 && candidates[startIndex - 1] >= startPrime) + { + startIndex--; + } + } + } + + List primesToTest = candidates; + Span primesSpan = CollectionsMarshal.AsSpan(primesToTest); ulong maxPrime = 0UL; + int primeWriteIndex = 0; using IEnumerator primeEnumerator = Prime.Numbers.GetEnumerator(); bool hasPrime = primeEnumerator.MoveNext(); ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL; - for (int index = 0; index < candidates.Count; index++) + for (int index = startIndex; index < candidateCount; index++) { - ulong candidate = candidates[index]; - - if (applyStartPrime && candidate < startPrime) - { - continue; - } - - if (previousResults is not null && previousResults.ContainsKey(candidate)) - { - skippedByPreviousResults++; - continue; - } + ulong candidate = primesSpan[index]; if (candidate <= 1UL) { @@ -76,17 +145,17 @@ public static class MersenneNumberDivisorByDivisorTester continue; } + primesSpan[primeWriteIndex++] = candidate; + if (candidate > maxPrime) { maxPrime = candidate; } - - primesToTest.Add(candidate); } - if (skippedByPreviousResults > 0) + if (primeWriteIndex < primesToTest.Count) { - Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results."); + primesToTest.RemoveRange(primeWriteIndex, primesToTest.Count - primeWriteIndex); } if (primesToTest.Count == 0) diff --git a/PerfectNumbers.Core/MontgomeryDivisorData.cs b/PerfectNumbers.Core/MontgomeryDivisorData.cs index 95ea18b..348471b 100644 --- a/PerfectNumbers.Core/MontgomeryDivisorData.cs +++ b/PerfectNumbers.Core/MontgomeryDivisorData.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -50,8 +50,27 @@ public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong internal static class MontgomeryDivisorDataCache { - private static readonly ConcurrentDictionary Cache = new(); + [ThreadStatic] + private static bool s_hasCachedValue; + + [ThreadStatic] + private static ulong s_cachedModulus; + + [ThreadStatic] + private static MontgomeryDivisorData s_cachedData; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MontgomeryDivisorData Get(ulong modulus) => Cache.GetOrAdd(modulus, static m => MontgomeryDivisorData.FromModulus(m)); + public static MontgomeryDivisorData Get(ulong modulus) + { + if (s_hasCachedValue && s_cachedModulus == modulus) + { + return s_cachedData; + } + + MontgomeryDivisorData computed = MontgomeryDivisorData.FromModulus(modulus); + s_cachedModulus = modulus; + s_cachedData = computed; + s_hasCachedValue = true; + return computed; + } }