diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 3f5773e..5ec3245 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -1057,13 +1057,13 @@ internal static partial class PrimeOrderCalculator // Span exponentSlots = exponentSlotsArray.AsSpan(0, FactorSlotCount); Span primeSlots = stackalloc ulong[FactorSlotCount]; Span exponentSlots = stackalloc int[FactorSlotCount]; + ulong[]? primeSlotArray = null; + int[]? exponentSlotArray = null; // We don't need to worry about leftovers, because we always use indexes within the calculated counts // primeSlots.Clear(); // exponentSlots.Clear(); int factorCount = 0; - Dictionary? counts = null; - bool useDictionary = false; uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit; ulong remaining = value; @@ -1083,41 +1083,15 @@ internal static partial class PrimeOrderCalculator if (!gpuFactored) { - // We don't need to worry about leftovers, because we always use indexes within the calculated counts - // primeSlots.Clear(); - // exponentSlots.Clear(); - - counts = ThreadStaticPools.RentUlongIntDictionary(Math.Max(FactorSlotCount, 8)); - counts.Clear(); - - // bool gpuPopulated = TryPopulateSmallPrimeFactorsGpu(value, limit, primeSlots, exponentSlots, out factorCount, out remaining); - // if (!gpuPopulated) - // { - // throw new InvalidOperationException($"GPU didn't populate factors for {value}"); - // counts.Clear(); - remaining = PopulateSmallPrimeFactorsCpu(value, limit, counts); - // } - - int dictionaryCount = counts.Count; - if (dictionaryCount <= FactorSlotCount) - { - int copyIndex = 0; - foreach (KeyValuePair entry in counts) - { - primeSlots[copyIndex] = entry.Key; - exponentSlots[copyIndex] = entry.Value; - copyIndex++; - } - - factorCount = copyIndex; - ThreadStaticPools.ReturnUlongIntDictionary(counts); - counts = null; - } - else - { - useDictionary = true; - factorCount = dictionaryCount; - } + PopulateSmallPrimeFactorsCpu( + value, + limit, + ref primeSlots, + ref exponentSlots, + ref primeSlotArray, + ref exponentSlotArray, + out factorCount, + out remaining); } @@ -1157,7 +1131,7 @@ internal static partial class PrimeOrderCalculator if (isPrime) { - AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); + AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1); continue; } @@ -1229,7 +1203,7 @@ internal static partial class PrimeOrderCalculator if (entry.IsPrime) { - AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); + AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1); } // composite is never smaller on the execution path // else if (composite > 1UL) @@ -1264,33 +1238,15 @@ internal static partial class PrimeOrderCalculator ArrayPool pool = ThreadStaticPools.FactorEntryPool; bool temp; - if (useDictionary) + if (factorCount == 0) { - temp = counts is null || counts.Count == 0; - if (temp && cofactor == value) + if (cofactor == value) { result = PartialFactorResult.Rent(null, cofactor, false, 0, cofactorIsPrime); goto ReturnResult; } - if (temp) - { - result = PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0, cofactorIsPrime); - goto ReturnResult; - } - - FactorEntry[] factors = pool.Rent(counts!.Count); - index = 0; - foreach (KeyValuePair entry in counts) - { - factors[index] = new FactorEntry(entry.Key, entry.Value, true); - index++; - } - - Array.Sort(factors, static (a, b) => a.Value.CompareTo(b.Value)); - - temp = cofactor == 1UL; - result = PartialFactorResult.Rent(factors, cofactor, temp, index, cofactorIsPrime); + result = PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0, cofactorIsPrime); goto ReturnResult; } @@ -1330,14 +1286,14 @@ internal static partial class PrimeOrderCalculator result = PartialFactorResult.Rent(array, cofactor, temp, arrayIndex, cofactorIsPrime); ReturnResult: - // ThreadStaticPools.UlongPool.Return(primeSlotsArray); - // ThreadStaticPools.IntPool.Return(exponentSlotsArray); - - - if (counts is not null) + if (primeSlotArray is not null) { - counts.Clear(); - ThreadStaticPools.ReturnUlongIntDictionary(counts); + ThreadStaticPools.UlongPool.Return(primeSlotArray, clearArray: false); + } + + if (exponentSlotArray is not null) + { + ThreadStaticPools.IntPool.Return(exponentSlotArray, clearArray: false); } if (compositeStack is not null) @@ -1351,35 +1307,28 @@ internal static partial class PrimeOrderCalculator return result; } - - private static bool TryPopulateSmallPrimeFactorsCpu( - ulong value, - uint limit, - Span primeTargets, - Span exponentTargets, - out int factorCount, - out ulong remaining) + private static void PopulateSmallPrimeFactorsCpu( + ulong value, + uint limit, + ref Span primeTargets, + ref Span exponentTargets, + ref ulong[]? primeArray, + ref int[]? exponentArray, + out int factorCount, + out ulong remaining) { factorCount = 0; - remaining = value; + ulong remainingLocal = value; - // value will never <= 1 in production code - // if (value <= 1UL) - // { - // return true; - // } - - int capacity = Math.Min(primeTargets.Length, exponentTargets.Length); - // capacity will never equal 0 in production code - // if (capacity == 0) - // { - // return false; - // } + if (value <= 1UL) + { + remaining = remainingLocal; + return; + } uint[] primes = PrimesGenerator.SmallPrimes; ulong[] squares = PrimesGenerator.SmallPrimesPow2; int primeCount = primes.Length; - ulong remainingLocal = value; uint effectiveLimit = limit == 0 ? uint.MaxValue : limit; for (int i = 0; i < primeCount && remainingLocal > 1UL; i++) @@ -1391,19 +1340,11 @@ internal static partial class PrimeOrderCalculator } ulong primeSquare = squares[i]; - // primeSquare will never == 0 in production code - // if (primeSquare != 0 && primeSquare > remainingLocal) if (primeSquare > remainingLocal) { break; } - // primeCandidate will never equal 0 in production code - // if (primeCandidate == 0UL) - // { - // continue; - // } - ulong primeValue = primeCandidate; if ((remainingLocal % primeValue) != 0UL) { @@ -1416,10 +1357,13 @@ internal static partial class PrimeOrderCalculator continue; } - if (factorCount >= capacity) - { - throw new InvalidOperationException($"Capacity is smaller than factor count"); - } + EnsureCollectorCapacity( + ref primeTargets, + ref exponentTargets, + ref primeArray, + ref exponentArray, + factorCount, + factorCount + 1); primeTargets[factorCount] = primeValue; exponentTargets[factorCount] = exponent; @@ -1427,7 +1371,6 @@ internal static partial class PrimeOrderCalculator } remaining = remainingLocal; - return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1495,46 +1438,6 @@ internal static partial class PrimeOrderCalculator return exponent; } - private static ulong PopulateSmallPrimeFactorsCpu(ulong value, uint limit, Dictionary counts) - { - ulong remaining = value; - uint[] primes = PrimesGenerator.SmallPrimes; - ulong[] squares = PrimesGenerator.SmallPrimesPow2; - int primeCount = primes.Length; - - for (int i = 0; i < primeCount; i++) - { - uint primeCandidate = primes[i]; - if (primeCandidate > limit) - { - break; - } - - if (squares[i] > remaining) - { - break; - } - - if ((remaining % primeCandidate) != 0UL) - { - continue; - } - - int exponent = 0; - do - { - // TODO: Implement DivRem like solution or residue stepper to re-use previous iteration results in new calculation. Add / use method, likely to ULongExtensions - remaining /= primeCandidate; - exponent++; - } - while ((remaining % primeCandidate) == 0UL); - - counts[primeCandidate] = exponent; - } - - return remaining; - } - private static long CreateDeadlineTimestamp(int milliseconds) { if (milliseconds <= 0) @@ -1657,11 +1560,18 @@ internal static partial class PrimeOrderCalculator } } - private static bool TryAppendFactor(Span primes, Span exponents, ref int count, ulong prime, int exponent) + private static void AddFactorToCollector( + ref Span primes, + ref Span exponents, + ref ulong[]? primeArray, + ref int[]? exponentArray, + ref int count, + ulong prime, + int exponent) { if (exponent <= 0) { - return true; + return; } for (int i = 0; i < count; i++) @@ -1669,70 +1579,64 @@ internal static partial class PrimeOrderCalculator if (primes[i] == prime) { exponents[i] += exponent; - return true; + return; } } - if (count >= primes.Length) - { - return false; - } + EnsureCollectorCapacity( + ref primes, + ref exponents, + ref primeArray, + ref exponentArray, + count, + count + 1); primes[count] = prime; exponents[count] = exponent; count++; - return true; } - private static void AddFactorToCollector( - ref bool useDictionary, - ref Dictionary? counts, - Span primes, - Span exponents, - ref int count, - ulong prime, - int exponent) + private static void EnsureCollectorCapacity( + ref Span primes, + ref Span exponents, + ref ulong[]? primeArray, + ref int[]? exponentArray, + int count, + int requiredCapacity) { - if (exponent <= 0) + if (requiredCapacity <= primes.Length) { return; } - if (useDictionary) + int newCapacity = primes.Length << 1; + if (newCapacity < requiredCapacity) { - AddFactor(counts!, prime, exponent); - return; + newCapacity = requiredCapacity; } - if (TryAppendFactor(primes, exponents, ref count, prime, exponent)) + ulong[] newPrimeArray = ThreadStaticPools.UlongPool.Rent(newCapacity); + int[] newExponentArray = ThreadStaticPools.IntPool.Rent(newCapacity); + Span newPrimeSpan = newPrimeArray.AsSpan(); + Span newExponentSpan = newExponentArray.AsSpan(); + + primes.Slice(0, count).CopyTo(newPrimeSpan); + exponents.Slice(0, count).CopyTo(newExponentSpan); + + if (primeArray is not null) { - return; + ThreadStaticPools.UlongPool.Return(primeArray, clearArray: false); } - counts ??= ThreadStaticPools.RentUlongIntDictionary(Math.Max(count, 8)); - CopyFactorsToDictionary(primes, exponents, count, counts); - count = 0; - useDictionary = true; - AddFactor(counts, prime, exponent); - } - - private static void CopyFactorsToDictionary( - in ReadOnlySpan primes, - in ReadOnlySpan exponents, - int count, - Dictionary target) - { - for (int i = 0; i < count; i++) + if (exponentArray is not null) { - ulong prime = primes[i]; - int exponent = exponents[i]; - if (prime == 0UL || exponent == 0) - { - continue; - } - - target[prime] = exponent; + ThreadStaticPools.IntPool.Return(exponentArray, clearArray: false); } + + primeArray = newPrimeArray; + exponentArray = newExponentArray; + primes = newPrimeSpan; + exponents = newExponentSpan; } private static ulong CalculateByFactorizationCpu(ulong prime, in MontgomeryDivisorData divisorData) diff --git a/PerfectNumbers.Core/ThreadStaticPools.cs b/PerfectNumbers.Core/ThreadStaticPools.cs index b019cf8..3d5ca9b 100644 --- a/PerfectNumbers.Core/ThreadStaticPools.cs +++ b/PerfectNumbers.Core/ThreadStaticPools.cs @@ -260,43 +260,6 @@ namespace PerfectNumbers.Core pool.Add(stack); } - [ThreadStatic] - private static List>? _ulongIntDictionaryPool; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Dictionary RentUlongIntDictionary(int capacityHint) - { - List>? pool = _ulongIntDictionaryPool; - if (pool is not null) - { - int poolCount = pool.Count; - if (poolCount > 0) - { - int lastIndex = poolCount - 1; - Dictionary dictionary = pool[lastIndex]; - pool.RemoveAt(lastIndex); - dictionary.EnsureCapacity(capacityHint); - - return dictionary; - } - } - - return new Dictionary(capacityHint); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnUlongIntDictionary(Dictionary dictionary) - { - List>? pool = _ulongIntDictionaryPool; - if (pool is null) - { - pool = new List>(4); - _ulongIntDictionaryPool = pool; - } - - pool.Add(dictionary); - } - [ThreadStatic] private static Dictionary? _factorCountDictionary;