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..b0e677e 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -322,6 +322,7 @@ public class MersenneDivisorCycles primeOrderFailed = false; } + factorCounts.Clear(); ThreadStaticPools.ReturnFactorCountDictionary(factorCounts); return success; } @@ -339,7 +340,7 @@ public class MersenneDivisorCycles bool success = TryFactorIntoCountsInternal(value, scratch); if (!success) { - // The scan recalculates factors every time; the old cache never produced hits. + scratch.Clear(); ThreadStaticPools.ReturnFactorScratchDictionary(scratch); return false; } @@ -350,6 +351,7 @@ public class MersenneDivisorCycles AddFactor(counts, pair.Key, pair.Value); } + scratch.Clear(); ThreadStaticPools.ReturnFactorScratchDictionary(scratch); return true; } @@ -459,47 +461,122 @@ 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; - for (int i = 0; i < count; i++) - { - 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; - } - } - - return order; + primes[index] = entry.Key; + index++; } - finally + + Array.Sort(primes, 0, count); + + const int StackMultiplicityCapacity = 16; + const int HeapMultiplicityCapacity = 256; + + // Scanner workloads keep almost all multiplicities within the stack-backed span, so the stack path handles the hot loop. + Span stackCandidates = stackalloc ulong[StackMultiplicityCapacity]; + Span stackEvaluations = stackalloc bool[StackMultiplicityCapacity]; + + ulong[]? heapCandidateArray = null; + bool[]? heapEvaluationArray = null; + + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + + ulong order = initialOrder; + for (int i = 0; i < count; i++) { - ArrayPool.Shared.Return(primes, clearArray: false); + 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 <= StackMultiplicityCapacity) + { + ProcessReduceOrderPrime(stackCandidates.Slice(0, multiplicity), stackEvaluations.Slice(0, multiplicity), ref order, prime, multiplicity, ref stepper); + continue; + } + + if (multiplicity > HeapMultiplicityCapacity) + { + throw new InvalidOperationException($"Factor multiplicity {multiplicity} exceeds the supported limit of {HeapMultiplicityCapacity}."); + } + + // Larger multiplicities appear during stress tests; reuse a shared heap buffer in those cases. + heapCandidateArray ??= ThreadStaticPools.UlongPool.Rent(HeapMultiplicityCapacity); + heapEvaluationArray ??= ThreadStaticPools.BoolPool.Rent(HeapMultiplicityCapacity); + ProcessReduceOrderPrime(heapCandidateArray.AsSpan(0, multiplicity), heapEvaluationArray.AsSpan(0, multiplicity), ref order, prime, multiplicity, ref stepper); + } + + ThreadStaticPools.ReturnExponentStepper(stepper); + ThreadStaticPools.UlongPool.Return(primes); + + if (heapCandidateArray is not null) + { + ThreadStaticPools.UlongPool.Return(heapCandidateArray); + ThreadStaticPools.BoolPool.Return(heapEvaluationArray!); + } + + return order; + } + + private static void ProcessReduceOrderPrime(Span candidateBuffer, Span evaluationBuffer, ref ulong order, ulong prime, int multiplicity, ref ExponentRemainderStepper stepper) + { + ulong working = order; + int actual = 0; + while (actual < multiplicity) + { + if (working < prime) + { + break; + } + + ulong reduced = working / prime; + 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]; } } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 76c583c..01975fe 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -1,3 +1,4 @@ +using System; using System.Buffers; using System.Diagnostics; using System.Numerics; @@ -110,27 +111,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 +204,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 +212,110 @@ internal static partial class PrimeOrderCalculator buffer.Slice(0, length).Sort(static (a, b) => a.Value.CompareTo(b.Value)); + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + + const int StackExponentCapacity = 16; + const int ExponentHardLimit = 256; + + Span stackCandidates = stackalloc ulong[StackExponentCapacity]; + Span stackEvaluations = stackalloc bool[StackExponentCapacity]; + + ulong[]? heapCandidateArray = null; + bool[]? heapEvaluationArray = null; + 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 > ExponentHardLimit) { - if ((order % primeFactor) != 0UL) - { - break; - } - - ulong reduced = order / primeFactor; - if (Pow2EqualsOneCpu(reduced, prime, divisorData)) - { - order = reduced; - continue; - } - - break; + throw new InvalidOperationException($"Prime factor exponent {exponent} exceeds the supported limit of {ExponentHardLimit}."); } + + if (exponent <= StackExponentCapacity) + { + ProcessExponentLoweringPrime(stackCandidates.Slice(0, exponent), stackEvaluations.Slice(0, exponent), ref order, primeFactor, exponent, ref stepper); + continue; + } + + heapCandidateArray ??= ThreadStaticPools.UlongPool.Rent(ExponentHardLimit); + heapEvaluationArray ??= ThreadStaticPools.BoolPool.Rent(ExponentHardLimit); + ProcessExponentLoweringPrime(heapCandidateArray.AsSpan(0, exponent), heapEvaluationArray.AsSpan(0, exponent), ref order, primeFactor, exponent, ref stepper); + } + + ThreadStaticPools.ReturnExponentStepper(stepper); + + if (heapCandidateArray is not null) + { + ThreadStaticPools.UlongPool.Return(heapCandidateArray); + ThreadStaticPools.BoolPool.Return(heapEvaluationArray!); } 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,28 +373,101 @@ internal static partial class PrimeOrderCalculator { ReadOnlySpan span = factorization.Factors!; int length = factorization.Count; + + const int StackExponentCapacity = 32; + const int ExponentHardLimit = 256; + + Span stackBuffer = stackalloc ulong[StackExponentCapacity]; + + ulong[]? heapCandidateArray = null; + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + bool violates = false; + 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++) + // Factor exponents emitted by TryConfirmCandidateCpu stay positive for production workloads. + // if (exponent <= 0) + // { + // continue; + // } + + if (exponent > ExponentHardLimit) { - // TODO: Benchmark and / or implement DivRem like solution or residue stepper? - if ((reduced % primeFactor) != 0UL) + throw new InvalidOperationException($"Factor exponent {exponent} exceeds the supported limit of {ExponentHardLimit}."); + } + + if (exponent <= StackExponentCapacity) + { + if (ValidateOrderForFactor(stackBuffer.Slice(0, exponent), primeFactor, exponent, order, ref stepper)) { + violates = true; break; } - reduced /= primeFactor; - if (Pow2EqualsOneCpu(reduced, prime, divisorData)) - { - return false; - } + continue; + } + + heapCandidateArray ??= ThreadStaticPools.UlongPool.Rent(ExponentHardLimit); + if (ValidateOrderForFactor(heapCandidateArray.AsSpan(0, exponent), primeFactor, exponent, order, ref stepper)) + { + violates = true; + break; } } - return true; + ThreadStaticPools.ReturnExponentStepper(stepper); + + if (heapCandidateArray is not null) + { + ThreadStaticPools.UlongPool.Return(heapCandidateArray); + } + + return !violates; + } + + private static bool ValidateOrderForFactor(Span buffer, ulong primeFactor, int exponent, ulong order, ref ExponentRemainderStepper stepper) + { + 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(); + + stepper.Reset(); + + for (int j = 0; j < actual; j++) + { + if (stepper.ComputeNextIsUnity(candidates[j])) + { + return true; + } + } + + return false; } private static bool TryHeuristicFinishCpu( @@ -328,6 +521,7 @@ internal static partial class PrimeOrderCalculator BuildCandidates(order, factorArray, orderFactors.Count, candidates, capacity); if (candidates.Count == 0) { + candidates.Clear(); ThreadStaticPools.ReturnUlongList(candidates); return false; } @@ -340,6 +534,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 +607,7 @@ internal static partial class PrimeOrderCalculator } else { - equalsOne = Pow2EqualsOneCpu(candidate, prime, divisorData); + equalsOne = powStepper.ComputeNextIsUnity(candidate); } if (!equalsOne) { @@ -429,7 +624,9 @@ internal static partial class PrimeOrderCalculator pool.Return(gpuPool, clearArray: false); } + candidates.Clear(); ThreadStaticPools.ReturnUlongList(candidates); + ThreadStaticPools.ReturnExponentStepper(powStepper); result = candidate; return true; } @@ -442,7 +639,9 @@ internal static partial class PrimeOrderCalculator index += batchSize; } + candidates.Clear(); ThreadStaticPools.ReturnUlongList(candidates); + ThreadStaticPools.ReturnExponentStepper(powStepper); // DebugLog("No candidate confirmed"); return false; } @@ -635,8 +834,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,33 +846,57 @@ internal static partial class PrimeOrderCalculator ReadOnlySpan span = factorization.Factors; int length = factorization.Count; + + const int StackExponentCapacity = 32; + const int ExponentHardLimit = 256; + + Span stackBuffer = stackalloc ulong[StackExponentCapacity]; + ulong[]? heapCandidateArray = null; + ExponentRemainderStepper stepper = ThreadStaticPools.RentExponentStepper(divisorData); + bool violates = false; + 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 > ExponentHardLimit) { - // TODO: Measure and implement DivRem like solution or residue stepper to reuse calculation results from previous iteration - if ((reduced % primeFactor) != 0UL) + throw new InvalidOperationException($"Candidate factor exponent {exponent} exceeds the supported limit of {ExponentHardLimit}."); + } + + if (exponent <= StackExponentCapacity) + { + if (CheckCandidateViolation(stackBuffer.Slice(0, exponent), primeFactor, exponent, candidate, prime, ref powUsed, powBudget, ref stepper)) { + violates = true; break; } - reduced /= primeFactor; - if (powUsed >= powBudget && powBudget > 0) - { - return false; - } + continue; + } - powUsed++; - if (Pow2EqualsOneCpu(reduced, prime, divisorData)) - { - return false; - } + heapCandidateArray ??= ThreadStaticPools.UlongPool.Rent(ExponentHardLimit); + if (CheckCandidateViolation(heapCandidateArray.AsSpan(0, exponent), primeFactor, exponent, candidate, prime, ref powUsed, powBudget, ref stepper)) + { + violates = true; + break; } } - return true; + ThreadStaticPools.ReturnExponentStepper(stepper); + + if (heapCandidateArray is not null) + { + ThreadStaticPools.UlongPool.Return(heapCandidateArray); + } + + return !violates; } finally { @@ -683,6 +904,55 @@ internal static partial class PrimeOrderCalculator } } + private static bool CheckCandidateViolation(Span buffer, ulong primeFactor, int exponent, ulong candidate, ulong prime, ref int powUsed, int powBudget, ref ExponentRemainderStepper stepper) + { + 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(); + + stepper.Reset(); + + for (int j = 0; j < actual; j++) + { + if (powUsed >= powBudget && powBudget > 0) + { + return true; + } + + powUsed++; + if (stepper.ComputeNextIsUnity(candidates[j])) + { + return true; + } + } + + return false; + } + private static bool Pow2EqualsOneCpu(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData) { if (IsGpuPow2Allowed) @@ -763,6 +1033,7 @@ internal static partial class PrimeOrderCalculator } factorCount = copyIndex; + counts.Clear(); ThreadStaticPools.ReturnUlongIntDictionary(counts); counts = null; } @@ -967,6 +1238,7 @@ internal static partial class PrimeOrderCalculator if (counts is not null) { + counts.Clear(); ThreadStaticPools.ReturnUlongIntDictionary(counts); } @@ -996,7 +1268,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..f8b07cc 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,21 @@ 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.Capacity < capacityHint) + { + list.Capacity = capacityHint; + } - if (list.Capacity < capacityHint) - { - list.Capacity = capacityHint; + return list; } - - return list; } return new List(capacityHint); @@ -151,22 +161,21 @@ 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.Capacity < capacityHint) + { + list.Capacity = capacityHint; + } - if (list.Capacity < capacityHint) - { - list.Capacity = capacityHint; + return list; } - - return list; } return new List(capacityHint); @@ -192,22 +201,22 @@ 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); - if (stack.Count < capacityHint) - { - stack.EnsureCapacity(capacityHint); - } + if (stack.Count < capacityHint) + { + stack.EnsureCapacity(capacityHint); + } - return stack; + return stack; + } } return new Stack(capacityHint); @@ -233,22 +242,18 @@ 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) - { - dictionary.Clear(); - } - - if (dictionary.Count < capacityHint) + int poolCount = pool.Count; + if (poolCount > 0) { + int lastIndex = poolCount - 1; + Dictionary dictionary = pool[lastIndex]; + pool.RemoveAt(lastIndex); dictionary.EnsureCapacity(capacityHint); - } - return dictionary; + return dictionary; + } } return new Dictionary(capacityHint); @@ -280,14 +285,12 @@ namespace PerfectNumbers.Core } _factorCountDictionary = null; - dictionary.Clear(); return dictionary; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReturnFactorCountDictionary(Dictionary dictionary) { - dictionary.Clear(); _factorCountDictionary = dictionary; } @@ -304,15 +307,41 @@ namespace PerfectNumbers.Core } _factorScratchDictionary = null; - dictionary.Clear(); return dictionary; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ReturnFactorScratchDictionary(Dictionary dictionary) { - dictionary.Clear(); _factorScratchDictionary = dictionary; } + + [ThreadStatic] + private static ExponentRemainderStepper _exponentRemainderStepper; + + [ThreadStatic] + private static bool _hasExponentRemainderStepper; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ExponentRemainderStepper RentExponentStepper(in MontgomeryDivisorData divisorData) + { + if (_hasExponentRemainderStepper && _exponentRemainderStepper.MatchesDivisor(divisorData)) + { + ExponentRemainderStepper stepper = _exponentRemainderStepper; + _hasExponentRemainderStepper = false; + stepper.Reset(); + return stepper; + } + + return new ExponentRemainderStepper(divisorData); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ReturnExponentStepper(ExponentRemainderStepper stepper) + { + stepper.Reset(); + _exponentRemainderStepper = stepper; + _hasExponentRemainderStepper = true; + } } }