Use span growth instead of dictionaries in partial factor

This commit is contained in:
Marek Lemańczyk authored and mlemanczyk committed 2025-11-02 11:22:38 +01:00
1 parent ab6a416de2
commit 5339c3c304
2 files changed
+101 -234

No files matched your search

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