diff --git a/PerfectNumbers.Core/ExponentRemainderStepper.cs b/PerfectNumbers.Core/ExponentRemainderStepper.cs index bf1e888..dda453c 100644 --- a/PerfectNumbers.Core/ExponentRemainderStepper.cs +++ b/PerfectNumbers.Core/ExponentRemainderStepper.cs @@ -27,6 +27,11 @@ internal struct ExponentRemainderStepper public ulong PreviousExponent => _previousExponent; + public bool MatchesDivisor(in MontgomeryDivisorData divisor) + { + return _modulus == divisor.Modulus && _nPrime == divisor.NPrime && _montgomeryOne == divisor.MontgomeryOne; + } + public void Reset() { _previousExponent = 0UL; diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 36dcf77..10e86cc 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -304,7 +304,8 @@ public class MersenneDivisorCycles // return false; // } - Dictionary factorCounts = ThreadStaticPools.RentFactorCountDictionary(); + // Each divisor candidate is evaluated once along the scan, so a fresh factor map per call avoids keeping an unused cache. + Dictionary factorCounts = new Dictionary(); bool success = false; if (twoCount > 0) @@ -322,7 +323,6 @@ public class MersenneDivisorCycles primeOrderFailed = false; } - ThreadStaticPools.ReturnFactorCountDictionary(factorCounts); return success; } @@ -335,12 +335,11 @@ public class MersenneDivisorCycles return true; } - Dictionary scratch = ThreadStaticPools.RentFactorScratchDictionary(); + // Factorizations are local to a single reduction attempt; there is no reuse across calls. + Dictionary scratch = new Dictionary(); bool success = TryFactorIntoCountsInternal(value, scratch); if (!success) { - // The scan recalculates factors every time; the old cache never produced hits. - ThreadStaticPools.ReturnFactorScratchDictionary(scratch); return false; } @@ -350,7 +349,6 @@ public class MersenneDivisorCycles AddFactor(counts, pair.Key, pair.Value); } - ThreadStaticPools.ReturnFactorScratchDictionary(scratch); return true; } @@ -459,48 +457,115 @@ public class MersenneDivisorCycles } int count = factorCounts.Count; - ulong[] primes = ArrayPool.Shared.Rent(count); - try + ulong[] primes = ThreadStaticPools.UlongPool.Rent(count); + + int index = 0; + foreach (KeyValuePair entry in factorCounts) { - int index = 0; - foreach (KeyValuePair entry in factorCounts) + primes[index] = entry.Key; + index++; + } + + Array.Sort(primes, 0, count); + + ulong order = initialOrder; + + int stackMultiplicity = 0; + for (int i = 0; i < count; i++) + { + int multiplicity = factorCounts[primes[i]]; + if (multiplicity <= 16 && multiplicity > stackMultiplicity) { - primes[index] = entry.Key; - index++; + stackMultiplicity = multiplicity; + } + } + + Span stackCandidates = stackMultiplicity > 0 ? stackalloc ulong[stackMultiplicity] : Span.Empty; + Span stackEvaluations = stackMultiplicity > 0 ? stackalloc bool[stackMultiplicity] : Span.Empty; + + for (int i = 0; i < count; i++) + { + ulong prime = primes[i]; + int multiplicity = factorCounts[prime]; + // Multiplicities collected by TryFactorIntoCountsInternal are strictly positive along the scanner path. + // Keeping the guard commented documents the assumption so it does not resurface in future reviews. + // if (multiplicity <= 0) + // { + // continue; + // } + + if (multiplicity <= 16) + { + ProcessReduceOrderPrime(stackCandidates.Slice(0, multiplicity), stackEvaluations.Slice(0, multiplicity), ref order, prime, multiplicity, divisorData); + } + else + { + ulong[] rentedCandidates = ThreadStaticPools.UlongPool.Rent(multiplicity); + bool[] rentedEvaluations = ThreadStaticPools.BoolPool.Rent(multiplicity); + ProcessReduceOrderPrime(rentedCandidates.AsSpan(0, multiplicity), rentedEvaluations.AsSpan(0, multiplicity), ref order, prime, multiplicity, divisorData); + ThreadStaticPools.UlongPool.Return(rentedCandidates, clearArray: false); + ThreadStaticPools.BoolPool.Return(rentedEvaluations, clearArray: false); + } + } + + ThreadStaticPools.UlongPool.Return(primes, clearArray: false); + return order; + } + + private static void ProcessReduceOrderPrime(Span candidateBuffer, Span evaluationBuffer, ref ulong order, ulong prime, int multiplicity, in MontgomeryDivisorData divisorData) + { + ulong working = order; + int actual = 0; + while (actual < multiplicity) + { + if (working < prime) + { + break; } - Array.Sort(primes, 0, count); - - ulong order = initialOrder; - for (int i = 0; i < count; i++) + ulong reduced = working / prime; + if (reduced == 0UL) { - ulong prime = primes[i]; - int multiplicity = factorCounts[prime]; - - for (int iteration = 0; iteration < multiplicity; iteration++) - { - if (order % prime != 0UL) - { - break; - } - - ulong candidate = order / prime; - if (candidate.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL) - { - order = candidate; - continue; - } - - break; - } + break; } - return order; + candidateBuffer[actual] = reduced; + working = reduced; + actual++; } - finally + + if (actual == 0) { - ArrayPool.Shared.Return(primes, clearArray: false); + return; } + + Span candidates = candidateBuffer.Slice(0, actual); + Span evaluations = evaluationBuffer.Slice(0, actual); + + for (int left = 0, right = actual - 1; left < right; left++, right--) + { + ulong tmp = candidates[left]; + candidates[left] = candidates[right]; + candidates[right] = tmp; + } + + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + for (int j = 0; j < actual; j++) + { + evaluations[j] = stepper.ComputeNextIsUnity(candidates[j]); + } + + for (int j = actual - 1; j >= 0; j--) + { + if (!evaluations[j]) + { + break; + } + + order = candidates[j]; + } + + ThreadStaticPools.ReturnExponentStepper(stepper); } private static ulong PollardRho64(ulong n) diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 940c2f2..fc22d4b 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -110,27 +110,70 @@ internal static partial class PrimeOrderCalculator private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) { int length = factors.Count; + // The phi factorization on the scanner path always yields at least one entry for phi >= 2, + // preventing the zero-length case from occurring outside synthetic tests. + // if (length == 0) + // { + // return true; + // } + ReadOnlySpan factorSpan = new(factors.Factors, 0, length); - for (int i = 0; i < length; i++) + + if (length <= 32) { - ulong factor = factorSpan[i].Value; - // Don't uncomment this. If there is division by zero, that means something wrong. - // if (factor == 0UL) + Span stackBuffer = stackalloc ulong[length]; + return EvaluateSpecialMaxCandidates(stackBuffer, factorSpan, phi, prime, divisorData); + } + + ulong[] rented = ThreadStaticPools.UlongPool.Rent(length); + bool result = EvaluateSpecialMaxCandidates(rented.AsSpan(0, length), factorSpan, phi, prime, divisorData); + ThreadStaticPools.UlongPool.Return(rented, clearArray: false); + return result; + } + + private static bool EvaluateSpecialMaxCandidates(Span buffer, ReadOnlySpan factors, ulong phi, ulong prime, in MontgomeryDivisorData divisorData) + { + int actual = 0; + int factorCount = factors.Length; + for (int i = 0; i < factorCount; i++) + { + ulong factor = factors[i].Value; + // The partial factor pipeline never feeds zero or oversized factors while scanning candidate orders. + // if (factor == 0UL || factor > phi) // { // continue; // } - // TODO: Can we optimize it thanks to knowing the factors, differences between factors, factors and / or phi - // being monotonically increasing etc. For example, if phi increases and factor increases, what does it tell us - // about the value of reduced? Or, maybe wee can use residue stepping to calculate the residue instead of calling - // Pow2equalsOneCpu? Maybe we don't need to calculate it from scratch? ulong reduced = phi / factor; - if (Pow2EqualsOneCpu(reduced, prime, divisorData)) + if (reduced == 0UL) { + continue; + } + + buffer[actual] = reduced; + actual++; + } + + if (actual == 0) + { + return true; + } + + Span candidates = buffer.Slice(0, actual); + candidates.Sort(); + + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + int candidateCount = candidates.Length; + for (int i = 0; i < candidateCount; i++) + { + if (stepper.ComputeNextIsUnity(candidates[i])) + { + ThreadStaticPools.ReturnExponentStepper(stepper); return false; } } + ThreadStaticPools.ReturnExponentStepper(stepper); return true; } @@ -160,8 +203,6 @@ internal static partial class PrimeOrderCalculator Span buffer = tempArray; factorSpan.CopyTo(buffer); - // bool isPrime = PrimeTester.IsPrimeInternal(factors.Cofactor, CancellationToken.None); - // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factors.Cofactor); if (!factors.FullyFactored && factors.Cofactor > 1UL && factors.CofactorIsPrime) { buffer[length] = new FactorEntry(factors.Cofactor, 1, true); @@ -170,32 +211,105 @@ internal static partial class PrimeOrderCalculator buffer.Slice(0, length).Sort(static (a, b) => a.Value.CompareTo(b.Value)); + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + + int maxStackExponent = 0; + for (int i = 0; i < length; i++) + { + int exponent = buffer[i].Exponent; + if (exponent <= 16 && exponent > maxStackExponent) + { + maxStackExponent = exponent; + } + } + + Span stackCandidates = maxStackExponent > 0 ? stackalloc ulong[maxStackExponent] : Span.Empty; + Span stackEvaluations = maxStackExponent > 0 ? stackalloc bool[maxStackExponent] : Span.Empty; + for (int i = 0; i < length; i++) { ulong primeFactor = buffer[i].Value; int exponent = buffer[i].Exponent; - for (int iteration = 0; iteration < exponent; iteration++) + // Factor exponents produced by Pollard-Rho and trial division are always positive in the scanner flow. + // if (exponent <= 0) + // { + // continue; + // } + + if (exponent <= 16) { - if ((order % primeFactor) != 0UL) - { - break; - } - - ulong reduced = order / primeFactor; - if (Pow2EqualsOneCpu(reduced, prime, divisorData)) - { - order = reduced; - continue; - } - - break; + ProcessExponentLoweringPrime(stackCandidates.Slice(0, exponent), stackEvaluations.Slice(0, exponent), ref order, primeFactor, exponent, ref stepper); + } + else + { + ulong[] rentedCandidates = ThreadStaticPools.UlongPool.Rent(exponent); + bool[] rentedEvaluations = ThreadStaticPools.BoolPool.Rent(exponent); + ProcessExponentLoweringPrime(rentedCandidates.AsSpan(0, exponent), rentedEvaluations.AsSpan(0, exponent), ref order, primeFactor, exponent, ref stepper); + ThreadStaticPools.UlongPool.Return(rentedCandidates, clearArray: false); + ThreadStaticPools.BoolPool.Return(rentedEvaluations, clearArray: false); } } + ThreadStaticPools.ReturnExponentStepper(stepper); pool.Return(tempArray, clearArray: false); return order; } + private static void ProcessExponentLoweringPrime(Span candidateBuffer, Span evaluationBuffer, ref ulong order, ulong primeFactor, int exponent, ref ExponentRemainderStepper stepper) + { + ulong working = order; + int actual = 0; + while (actual < exponent) + { + if (working < primeFactor) + { + break; + } + + ulong reduced = working / primeFactor; + if (reduced == 0UL) + { + break; + } + + candidateBuffer[actual] = reduced; + working = reduced; + actual++; + } + + if (actual == 0) + { + return; + } + + Span candidates = candidateBuffer.Slice(0, actual); + Span evaluations = evaluationBuffer.Slice(0, actual); + + for (int left = 0, right = actual - 1; left < right; left++, right--) + { + ulong tmp = candidates[left]; + candidates[left] = candidates[right]; + candidates[right] = tmp; + } + + stepper.Reset(); + + for (int j = 0; j < actual; j++) + { + evaluations[j] = stepper.ComputeNextIsUnity(candidates[j]); + } + + for (int j = actual - 1; j >= 0; j--) + { + if (!evaluations[j]) + { + break; + } + + order = candidates[j]; + } + } + private static bool TryConfirmOrderCpu( ulong prime, ulong order, @@ -253,30 +367,95 @@ internal static partial class PrimeOrderCalculator { ReadOnlySpan span = factorization.Factors!; int length = factorization.Count; + + int maxStackExponent = 0; + for (int i = 0; i < length; i++) + { + int exponent = span[i].Exponent; + if (exponent <= 32 && exponent > maxStackExponent) + { + maxStackExponent = exponent; + } + } + + Span stackBuffer = maxStackExponent > 0 ? stackalloc ulong[maxStackExponent] : Span.Empty; + for (int i = 0; i < length; i++) { ulong primeFactor = span[i].Value; - ulong reduced = order; int exponent = span[i].Exponent; - for (int iteration = 0; iteration < exponent; iteration++) - { - // TODO: Benchmark and / or implement DivRem like solution or residue stepper? - if ((reduced % primeFactor) != 0UL) - { - break; - } + // Factor exponents emitted by TryConfirmCandidateCpu stay positive for production workloads. + // if (exponent <= 0) + // { + // continue; + // } - reduced /= primeFactor; - if (Pow2EqualsOneCpu(reduced, prime, divisorData)) + if (exponent <= 32) + { + if (ValidateOrderForFactor(stackBuffer.Slice(0, exponent), primeFactor, exponent, order, divisorData)) { return false; } } + else + { + ulong[] rentedCandidates = ThreadStaticPools.UlongPool.Rent(exponent); + if (ValidateOrderForFactor(rentedCandidates.AsSpan(0, exponent), primeFactor, exponent, order, divisorData)) + { + ThreadStaticPools.UlongPool.Return(rentedCandidates, clearArray: false); + return false; + } + ThreadStaticPools.UlongPool.Return(rentedCandidates, clearArray: false); + } } return true; } + private static bool ValidateOrderForFactor(Span buffer, ulong primeFactor, int exponent, ulong order, in MontgomeryDivisorData divisorData) + { + ulong working = order; + int actual = 0; + while (actual < exponent) + { + if (working < primeFactor) + { + break; + } + + ulong reduced = working / primeFactor; + if (reduced == 0UL) + { + break; + } + + buffer[actual] = reduced; + working = reduced; + actual++; + } + + if (actual == 0) + { + return false; + } + + Span candidates = buffer.Slice(0, actual); + candidates.Reverse(); + + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + for (int j = 0; j < actual; j++) + { + if (stepper.ComputeNextIsUnity(candidates[j])) + { + ThreadStaticPools.ReturnExponentStepper(stepper); + return true; + } + } + + ThreadStaticPools.ReturnExponentStepper(stepper); + return false; + } + private static bool TryHeuristicFinishCpu( ulong prime, ulong order, @@ -340,6 +519,7 @@ internal static partial class PrimeOrderCalculator int candidateCount = candidates.Count; bool allowGpuBatch = true; Span candidateSpan = CollectionsMarshal.AsSpan(candidates); + ExponentRemainderStepper powStepper = ThreadStaticPools.RentExponentStepper(divisorData); // DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)"); int index = 0; @@ -412,7 +592,7 @@ internal static partial class PrimeOrderCalculator } else { - equalsOne = Pow2EqualsOneCpu(candidate, prime, divisorData); + equalsOne = powStepper.ComputeNextIsUnity(candidate); } if (!equalsOne) { @@ -430,6 +610,7 @@ internal static partial class PrimeOrderCalculator } ThreadStaticPools.ReturnUlongList(candidates); + ThreadStaticPools.ReturnExponentStepper(powStepper); result = candidate; return true; } @@ -443,6 +624,7 @@ internal static partial class PrimeOrderCalculator } ThreadStaticPools.ReturnUlongList(candidates); + ThreadStaticPools.ReturnExponentStepper(powStepper); // DebugLog("No candidate confirmed"); return false; } @@ -635,8 +817,6 @@ internal static partial class PrimeOrderCalculator return false; } - // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factorization.Cofactor); - // bool isPrime = PrimeTester.IsPrimeInternal(factorization.Cofactor, CancellationToken.None); if (!factorization.CofactorIsPrime) { return false; @@ -649,29 +829,45 @@ internal static partial class PrimeOrderCalculator ReadOnlySpan span = factorization.Factors; int length = factorization.Count; + + int maxStackExponent = 0; + for (int i = 0; i < length; i++) + { + int exponent = span[i].Exponent; + if (exponent <= 32 && exponent > maxStackExponent) + { + maxStackExponent = exponent; + } + } + + Span stackBuffer = maxStackExponent > 0 ? stackalloc ulong[maxStackExponent] : Span.Empty; + for (int i = 0; i < length; i++) { ulong primeFactor = span[i].Value; - ulong reduced = candidate; - for (int iteration = 0; iteration < span[i].Exponent; iteration++) + int exponent = span[i].Exponent; + // Factorization entries for candidate confirmation are always positive here. + // if (exponent <= 0) + // { + // continue; + // } + + if (exponent <= 32) { - // TODO: Measure and implement DivRem like solution or residue stepper to reuse calculation results from previous iteration - if ((reduced % primeFactor) != 0UL) - { - break; - } - - reduced /= primeFactor; - if (powUsed >= powBudget && powBudget > 0) + if (CheckCandidateViolation(stackBuffer.Slice(0, exponent), primeFactor, exponent, candidate, prime, divisorData, ref powUsed, powBudget)) { return false; } - - powUsed++; - if (Pow2EqualsOneCpu(reduced, prime, divisorData)) + } + else + { + ulong[] rentedCandidates = ThreadStaticPools.UlongPool.Rent(exponent); + if (CheckCandidateViolation(rentedCandidates.AsSpan(0, exponent), primeFactor, exponent, candidate, prime, divisorData, ref powUsed, powBudget)) { + ThreadStaticPools.UlongPool.Return(rentedCandidates, clearArray: false); return false; } + ThreadStaticPools.UlongPool.Return(rentedCandidates, clearArray: false); } } @@ -683,6 +879,58 @@ internal static partial class PrimeOrderCalculator } } + private static bool CheckCandidateViolation(Span buffer, ulong primeFactor, int exponent, ulong candidate, ulong prime, in MontgomeryDivisorData divisorData, ref int powUsed, int powBudget) + { + ulong working = candidate; + int actual = 0; + while (actual < exponent) + { + if (working < primeFactor) + { + break; + } + + ulong reduced = working / primeFactor; + if (reduced == 0UL) + { + break; + } + + buffer[actual] = reduced; + working = reduced; + actual++; + } + + if (actual == 0) + { + return false; + } + + Span candidates = buffer.Slice(0, actual); + candidates.Reverse(); + + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + for (int j = 0; j < actual; j++) + { + if (powUsed >= powBudget && powBudget > 0) + { + ThreadStaticPools.ReturnExponentStepper(stepper); + return true; + } + + powUsed++; + if (stepper.ComputeNextIsUnity(candidates[j])) + { + ThreadStaticPools.ReturnExponentStepper(stepper); + return true; + } + } + + ThreadStaticPools.ReturnExponentStepper(stepper); + return false; + } + + private static bool Pow2EqualsOneCpu(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData) { if (IsGpuPow2Allowed) @@ -993,7 +1241,7 @@ internal static partial class PrimeOrderCalculator // value will never <= 1 in production code // if (value <= 1UL) // { - // return true; + // return true; // } int capacity = Math.Min(primeTargets.Length, exponentTargets.Length); diff --git a/PerfectNumbers.Core/ThreadStaticPools.cs b/PerfectNumbers.Core/ThreadStaticPools.cs index 4a2bb97..8ba6845 100644 --- a/PerfectNumbers.Core/ThreadStaticPools.cs +++ b/PerfectNumbers.Core/ThreadStaticPools.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Collections.Generic; using System.Runtime.CompilerServices; using PerfectNumbers.Core.Cpu; @@ -30,7 +31,6 @@ namespace PerfectNumbers.Core } } - [ThreadStatic] private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession; @@ -66,6 +66,18 @@ namespace PerfectNumbers.Core } } + [ThreadStatic] + private static ArrayPool? _boolPool; + + public static ArrayPool BoolPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return _boolPool ??= ArrayPool.Create(); + } + } + [ThreadStatic] private static ArrayPool? _bytePool; @@ -102,7 +114,6 @@ namespace PerfectNumbers.Core } } - [ThreadStatic] private static List>? _ulongListPool; @@ -110,22 +121,26 @@ namespace PerfectNumbers.Core public static List RentUlongList(int capacityHint) { List>? pool = _ulongListPool; - if (pool is not null && pool.Count > 0) + if (pool is not null) { - int lastIndex = pool.Count - 1; - List list = pool[lastIndex]; - pool.RemoveAt(lastIndex); - if (list.Count > 0) + int poolCount = pool.Count; + if (poolCount > 0) { - list.Clear(); - } + int lastIndex = poolCount - 1; + List list = pool[lastIndex]; + pool.RemoveAt(lastIndex); + if (list.Count > 0) + { + list.Clear(); + } - if (list.Capacity < capacityHint) - { - list.Capacity = capacityHint; - } + if (list.Capacity < capacityHint) + { + list.Capacity = capacityHint; + } - return list; + return list; + } } return new List(capacityHint); @@ -151,22 +166,26 @@ namespace PerfectNumbers.Core internal static List RentPrimeOrderPendingEntryList(int capacityHint) { List>? pool = _primeOrderPendingEntryListPool; - if (pool is not null && pool.Count > 0) + if (pool is not null) { - int lastIndex = pool.Count - 1; - List list = pool[lastIndex]; - pool.RemoveAt(lastIndex); - if (list.Count > 0) + int poolCount = pool.Count; + if (poolCount > 0) { - list.Clear(); - } + int lastIndex = poolCount - 1; + List list = pool[lastIndex]; + pool.RemoveAt(lastIndex); + if (list.Count > 0) + { + list.Clear(); + } - if (list.Capacity < capacityHint) - { - list.Capacity = capacityHint; - } + if (list.Capacity < capacityHint) + { + list.Capacity = capacityHint; + } - return list; + return list; + } } return new List(capacityHint); @@ -192,22 +211,28 @@ namespace PerfectNumbers.Core public static Stack RentUlongStack(int capacityHint) { List>? pool = _ulongStackPool; - if (pool is not null && pool.Count > 0) + if (pool is not null) { - int lastIndex = pool.Count - 1; - Stack stack = pool[lastIndex]; - pool.RemoveAt(lastIndex); - if (stack.Count > 0) + int poolCount = pool.Count; + if (poolCount > 0) { - stack.Clear(); - } + int lastIndex = poolCount - 1; + Stack stack = pool[lastIndex]; + pool.RemoveAt(lastIndex); + int stackCount = stack.Count; + if (stackCount > 0) + { + stack.Clear(); + stackCount = 0; + } - if (stack.Count < capacityHint) - { - stack.EnsureCapacity(capacityHint); - } + if (stackCount < capacityHint) + { + stack.EnsureCapacity(capacityHint); + } - return stack; + return stack; + } } return new Stack(capacityHint); @@ -226,6 +251,7 @@ namespace PerfectNumbers.Core pool.Add(stack); } + // The factor-count scratch caches were removed because divisor factorizations never repeat across scan iterations. [ThreadStatic] private static List>? _ulongIntDictionaryPool; @@ -233,22 +259,23 @@ namespace PerfectNumbers.Core public static Dictionary RentUlongIntDictionary(int capacityHint) { List>? pool = _ulongIntDictionaryPool; - if (pool is not null && pool.Count > 0) + if (pool is not null) { - int lastIndex = pool.Count - 1; - Dictionary dictionary = pool[lastIndex]; - pool.RemoveAt(lastIndex); - if (dictionary.Count > 0) + int poolCount = pool.Count; + if (poolCount > 0) { - dictionary.Clear(); - } + int lastIndex = poolCount - 1; + Dictionary dictionary = pool[lastIndex]; + pool.RemoveAt(lastIndex); + if (dictionary.Count > 0) + { + dictionary.Clear(); + } - if (dictionary.Count < capacityHint) - { dictionary.EnsureCapacity(capacityHint); - } - return dictionary; + return dictionary; + } } return new Dictionary(capacityHint); @@ -268,51 +295,31 @@ namespace PerfectNumbers.Core } [ThreadStatic] - private static Dictionary? _factorCountDictionary; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Dictionary RentFactorCountDictionary() - { - Dictionary? dictionary = _factorCountDictionary; - if (dictionary is null) - { - return new Dictionary(8); - } - - _factorCountDictionary = null; - dictionary.Clear(); - return dictionary; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnFactorCountDictionary(Dictionary dictionary) - { - dictionary.Clear(); - _factorCountDictionary = dictionary; - } + private static ExponentRemainderStepper _exponentRemainderStepper; [ThreadStatic] - private static Dictionary? _factorScratchDictionary; + private static bool _hasExponentRemainderStepper; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Dictionary RentFactorScratchDictionary() + internal static ExponentRemainderStepper RentExponentStepper(in MontgomeryDivisorData divisorData) { - Dictionary? dictionary = _factorScratchDictionary; - if (dictionary is null) + if (_hasExponentRemainderStepper && _exponentRemainderStepper.MatchesDivisor(divisorData)) { - return new Dictionary(8); + ExponentRemainderStepper stepper = _exponentRemainderStepper; + _hasExponentRemainderStepper = false; + stepper.Reset(); + return stepper; } - _factorScratchDictionary = null; - dictionary.Clear(); - return dictionary; + return new ExponentRemainderStepper(divisorData); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnFactorScratchDictionary(Dictionary dictionary) + internal static void ReturnExponentStepper(ExponentRemainderStepper stepper) { - dictionary.Clear(); - _factorScratchDictionary = dictionary; + stepper.Reset(); + _exponentRemainderStepper = stepper; + _hasExponentRemainderStepper = true; } } }