Reusable buffers introduced.

This commit is contained in:
mlemanczyk committed 2025-12-13 04:16:05 +01:00
1 parent fd58079c5a
commit 39a66fdef1
8 files changed
+829 -1056

No files matched your search

+147 -177
View File
@@ -76,12 +76,12 @@ internal static partial class PrimeOrderCalculator
return gpuOrder;
}
PartialFactorResult phiFactors = PartialFactorGpu(gpu, phi, config);
PartialFactorResult phiFactors = PartialFactorGpu(gpu, divisorData, phi, config);
ulong result;
if (phiFactors.Factors is null)
if (phiFactors.Count == 0)
{
result = CalculateByFactorizationCpu(prime, divisorData);
result = CalculateByFactorizationCpu(prime, divisorData, phiFactors);
phiFactors.Dispose();
return result;
@@ -147,21 +147,28 @@ internal static partial class PrimeOrderCalculator
UInt128 phi = prime - UInt128.One;
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
UInt128 result;
if (phiFactors.Factors is null)
try
{
result = FinishStrictlyWideGpu(gpu, prime, divisorData);
}
else
{
result = RunHeuristicPipelineWideGpu(gpu, prime, previousOrder, divisorData, config, phi, phiFactors);
}
UInt128 result;
if (phiFactors.Count == 0)
{
result = FinishStrictlyWideGpu(gpu, prime, divisorData);
}
else
{
result = RunHeuristicPipelineWideGpu(gpu, prime, previousOrder, divisorData, config, phi, phiFactors);
}
return result;
return result;
}
finally
{
phiFactors.Dispose();
}
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static void CollectFactorsGpu(PrimeOrderCalculatorAccelerator gpu, ref Span<ulong> primeSlots, ref Span<int> exponentSlots, ref ulong[] primeSlotArray, ref int[] exponentSlotArray, ref int factorCount, List<PendingEntry> pending, Stack<ulong> compositeStack, long deadlineTimestamp, ref bool pollardRhoDeadlineReached)
private static void CollectFactorsGpu(PrimeOrderCalculatorAccelerator gpu, ref Span<ulong> primeSlots, ref Span<int> exponentSlots, ref int factorCount, List<PartialFactorPendingEntry> pending, Stack<ulong> compositeStack, long deadlineTimestamp, ref bool pollardRhoDeadlineReached)
{
while (compositeStack.Count > 0)
{
@@ -170,7 +177,7 @@ internal static partial class PrimeOrderCalculator
if (isPrime)
{
AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1);
AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite);
continue;
}
@@ -178,14 +185,14 @@ internal static partial class PrimeOrderCalculator
if (currentTimestamp > deadlineTimestamp)
{
pollardRhoDeadlineReached = true;
pending.Add(new PendingEntry(composite, knownComposite: true));
pending.Add(new PartialFactorPendingEntry(composite, knownComposite: true));
continue;
}
// if (!TryPollardRhoGpu(gpu, composite, out ulong factor))
if (!TryPollardRhoCpu(composite, deadlineTimestamp, out ulong factor))
{
pending.Add(new PendingEntry(composite, knownComposite: true));
pending.Add(new PartialFactorPendingEntry(composite, knownComposite: true));
continue;
}
@@ -200,7 +207,6 @@ internal static partial class PrimeOrderCalculator
PrimeOrderCalculatorAccelerator gpu,
ReadOnlySpan<ulong> factors,
ulong phi,
ulong prime,
in MontgomeryDivisorData divisorData)
{
int factorCount = factors.Length;
@@ -235,7 +241,6 @@ internal static partial class PrimeOrderCalculator
private static UInt128 ExponentLoweringWideGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 order, in UInt128 prime, in MontgomeryDivisorData? divisorData, in PartialFactorResult128 factors)
{
ArrayPool<FactorEntry128> pool = ThreadStaticPools.FactorEntry128Pool;
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
int length = factors.Count;
// TODO: This should never trigger from production code - check
@@ -243,9 +248,7 @@ internal static partial class PrimeOrderCalculator
// {
// return order;
// }
FactorEntry128[] tempArray = pool.Rent(length + 1);
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
Span<FactorEntry128> buffer = factors.TempFactors.AsSpan(0, length + 1);
factorSpan.CopyTo(buffer);
if (!factors.FullyFactored && factors.Cofactor > UInt128.One && IsPrimeWide(factors.Cofactor))
@@ -263,7 +266,7 @@ internal static partial class PrimeOrderCalculator
int exponent = buffer[i].Exponent;
for (int iteration = 0; iteration < exponent; iteration++)
{
if ((order % primeFactor) == UInt128.Zero)
if (order.ReduceCycleRemainder(primeFactor) == UInt128.Zero)
{
UInt128 reduced = order / primeFactor;
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
@@ -277,7 +280,6 @@ internal static partial class PrimeOrderCalculator
}
}
pool.Return(tempArray, clearArray: false);
return order;
}
@@ -302,7 +304,7 @@ internal static partial class PrimeOrderCalculator
int exponent = entries[i].Value;
for (int iteration = 0; iteration < exponent; iteration++)
{
if ((order % primeFactor) != UInt128.Zero)
if (order.ReduceCycleRemainder(primeFactor) != UInt128.Zero)
{
UInt128 candidate = order / primeFactor;
if (Pow2ModWideGpu(gpu, candidate, prime, divisorData) == UInt128.One)
@@ -337,28 +339,26 @@ internal static partial class PrimeOrderCalculator
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static PartialFactorResult PartialFactorGpu(PrimeOrderCalculatorAccelerator gpu, ulong value, in PrimeOrderCalculatorConfig config)
private static PartialFactorResult PartialFactorGpu(PrimeOrderCalculatorAccelerator gpu, in MontgomeryDivisorData divisorData, ulong value, in PrimeOrderCalculatorConfig config)
{
if (value <= 1UL)
{
{
return PartialFactorResult.Empty;
}
// stackalloc is faster than pooling
Span<ulong> primeSlots = stackalloc ulong[PrimeOrderConstants.GpuSmallPrimeFactorSlots];
Span<int> exponentSlots = stackalloc int[PrimeOrderConstants.GpuSmallPrimeFactorSlots];
ulong[] primeSlotArray = [];
int[] exponentSlotArray = [];
PartialFactorResult result = PartialFactorResult.Rent(divisorData);
Span<ulong> primeSlots = new(result.Factors);
Span<int> exponentSlots = new (result.Exponents);
// We don't need to worry about leftovers, because we always use indexes within the calculated counts
// primeSlots.Clear();
// exponentSlots.Clear();
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
ulong remaining = value;
uint limit = config.SmallFactorLimit;
bool gpuFactored = PrimeOrderGpuHeuristics.TryPartialFactor(gpu, value, limit, primeSlots, exponentSlots, out int factorCount, out ulong remaining);
bool gpuFactored = PrimeOrderGpuHeuristics.TryPartialFactor(gpu, value, limit, primeSlots, exponentSlots, out int factorCount, out remaining);
List<PendingEntry> pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(2);
List<PartialFactorPendingEntry> pending = result.Pending;
pending.Clear();
if (!gpuFactored)
{
@@ -371,31 +371,28 @@ internal static partial class PrimeOrderCalculator
out remaining);
}
bool limitReached;
if (remaining > 1UL)
{
if (config.PollardRhoMilliseconds > 0)
{
long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds);
FixedCapacityStack<ulong> compositeStack = ThreadStaticPools.RentUlongStack(4);
FixedCapacityStack<ulong> compositeStack = result.CompositeStack;
compositeStack.Clear();
compositeStack.Push(remaining);
CollectFactorsCpu(primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached);
CollectFactorsCpu(primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out bool limitReached);
if (limitReached)
{
while (compositeStack.Count > 0)
{
pending.Add(new PendingEntry(compositeStack.Pop(), knownComposite: false));
pending.Add(PartialFactorPendingEntry.Rent(compositeStack.Pop(), knownComposite: false));
}
}
compositeStack.Clear();
ThreadStaticPools.ReturnUlongStack(compositeStack);
}
else
{
pending.Add(new PendingEntry(remaining, knownComposite: false));
pending.Add(PartialFactorPendingEntry.Rent(remaining, knownComposite: false));
}
}
@@ -405,13 +402,14 @@ internal static partial class PrimeOrderCalculator
int index = 0;
for (; index < pendingCount; index++)
{
PendingEntry entry = pending[index];
PartialFactorPendingEntry entry = pending[index];
ulong composite = entry.Value;
if (entry.KnownComposite)
{
cofactor = checked(cofactor * composite);
cofactorContainsComposite = true;
PartialFactorPendingEntry.Return(entry);
continue;
}
@@ -425,7 +423,7 @@ internal static partial class PrimeOrderCalculator
if (entry.IsPrime)
{
AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1);
AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite);
}
// composite is never smaller on the execution path
// else if (composite > 1UL)
@@ -434,6 +432,8 @@ internal static partial class PrimeOrderCalculator
cofactor = checked(cofactor * composite);
cofactorContainsComposite = true;
}
PartialFactorPendingEntry.Return(entry);
}
bool cofactorIsPrime;
@@ -450,19 +450,6 @@ internal static partial class PrimeOrderCalculator
cofactorIsPrime = PrimeTesterByLastDigit.IsPrimeCpu(cofactor);
}
PartialFactorResult result;
if (factorCount == 0)
{
if (cofactor == value)
{
result = PartialFactorResult.Rent(cofactor, false, 0, cofactorIsPrime);
goto ReturnResult;
}
result = PartialFactorResult.Rent(cofactor, cofactor == 1UL, 0, cofactorIsPrime);
goto ReturnResult;
}
// This will never happen in production code. We'll always get at least 1 factor
// if (factorCount == 0)
// {
@@ -476,41 +463,13 @@ internal static partial class PrimeOrderCalculator
// goto ReturnResult;
// }
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool;
ArrayPool<int> intPool = ThreadStaticPools.IntPool;
ulong[] factorsArray = ulongPool.Rent(factorCount);
int[] exponentsArray = intPool.Rent(factorCount);
for (int i = 0; i < factorCount; i++)
{
factorsArray[i] = primeSlots[i];
exponentsArray[i] = exponentSlots[i];
// This will never happen on the execution path from production code
// if (primeValue == 0UL || exponentValue == 0)
// {
// throw new Exception("Prime value or exponent equals zero");
// continue;
// }
}
Array.Sort(factorsArray, exponentsArray, 0, factorCount);
Array.Sort(result.Factors, result.Exponents, 0, factorCount);
// Check if it was factored completely
limitReached = cofactor == 1UL;
result = PartialFactorResult.Rent(factorsArray, exponentsArray, cofactor, limitReached, factorCount, cofactorIsPrime);
ReturnResult:
if (primeSlotArray.Length > 0)
{
ThreadStaticPools.UlongPool.Return(primeSlotArray, clearArray: false);
ThreadStaticPools.IntPool.Return(exponentSlotArray!, clearArray: false);
}
pending.Clear();
ThreadStaticPools.ReturnPrimeOrderPendingEntryList(pending);
result.Cofactor = cofactor;
result.FullyFactored = cofactor == 1UL;
result.Count = factorCount;
result.CofactorIsPrime = cofactorIsPrime;
return result;
}
@@ -580,8 +539,9 @@ internal static partial class PrimeOrderCalculator
private static bool TryConfirmCandidateWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 candidate, in PrimeOrderCalculatorConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData? divisorData)
{
PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
if (factorization.Factors is null)
if (factorization.Count == 0)
{
factorization.Dispose();
return false;
}
@@ -589,45 +549,54 @@ internal static partial class PrimeOrderCalculator
{
if (factorization.Cofactor <= UInt128.One)
{
factorization.Dispose();
return false;
}
if (!IsPrimeWide(factorization.Cofactor))
{
factorization.Dispose();
return false;
}
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
factorization.WithAdditionalPrime(factorization.Cofactor);
}
ReadOnlySpan<FactorEntry128> span = factorization.Factors;
int length = factorization.Count;
for (int i = 0; i < length; i++)
try
{
UInt128 primeFactor = span[i].Value;
UInt128 reduced = candidate;
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
for (int i = 0; i < length; i++)
{
if ((reduced % primeFactor) != UInt128.Zero)
UInt128 primeFactor = span[i].Value;
UInt128 reduced = candidate;
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{
break;
}
if (reduced.ReduceCycleRemainder(primeFactor) != UInt128.Zero)
{
break;
}
reduced /= primeFactor;
if (powUsed >= powBudget && powBudget > 0)
{
return false;
}
reduced /= primeFactor;
if (powUsed >= powBudget && powBudget > 0)
{
return false;
}
powUsed++;
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
{
return false;
powUsed++;
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
{
return false;
}
}
}
}
return true;
return true;
}
finally
{
factorization.Dispose();
}
}
private static bool TryConfirmOrderWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 order, in MontgomeryDivisorData? divisorData, in PrimeOrderCalculatorConfig config)
@@ -643,8 +612,9 @@ internal static partial class PrimeOrderCalculator
}
PartialFactorResult128 factorization = PartialFactorWide(order, config);
if (factorization.Factors is null)
if (factorization.Count == 0)
{
factorization.Dispose();
return false;
}
@@ -652,15 +622,17 @@ internal static partial class PrimeOrderCalculator
{
if (factorization.Cofactor <= UInt128.One)
{
factorization.Dispose();
return false;
}
if (!IsPrimeWide(factorization.Cofactor))
{
factorization.Dispose();
return false;
}
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
factorization.WithAdditionalPrime(factorization.Cofactor);
}
ReadOnlySpan<FactorEntry128> span = factorization.Factors;
@@ -679,11 +651,13 @@ internal static partial class PrimeOrderCalculator
reduced /= primeFactor;
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
{
factorization.Dispose();
return false;
}
}
}
factorization.Dispose();
return true;
}
@@ -695,7 +669,6 @@ internal static partial class PrimeOrderCalculator
ulong? previousOrder,
in MontgomeryDivisorData divisorData,
in PrimeOrderCalculatorConfig config,
PartialFactorResult phiFactors,
PartialFactorResult? cachedOrderFactors,
out ulong result)
{
@@ -707,10 +680,10 @@ internal static partial class PrimeOrderCalculator
}
// Reuse the partial factorization from TryConfirmOrderCpu when available.
PartialFactorResult orderFactors = cachedOrderFactors ?? PartialFactorGpu(gpu, order, config);
PartialFactorResult orderFactors = cachedOrderFactors ?? PartialFactorGpu(gpu, divisorData, order, config);
try
{
if (orderFactors.Factors is null)
if (orderFactors.Count == 0)
{
return false;
}
@@ -726,16 +699,13 @@ internal static partial class PrimeOrderCalculator
}
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity;
List<ulong> candidates = ThreadStaticPools.RentUlongList(capacity);
List<ulong> candidates = orderFactors.FactorCandidatesList;
candidates.Clear();
ulong[] factorArray = orderFactors.Factors!;
int[] exponentsArray = orderFactors.Exponents!;
BuildCandidates(order, factorArray, exponentsArray, orderFactors.Count, candidates, capacity);
BuildCandidates(order, orderFactors, orderFactors.Count, candidates, capacity);
int candidateCount = candidates.Count;
if (candidateCount == 0)
{
ThreadStaticPools.ReturnUlongList(candidates);
return false;
}
@@ -745,11 +715,12 @@ internal static partial class PrimeOrderCalculator
int powUsed = 0;
bool allowGpuBatch = true;
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
ExponentRemainderStepperCpu powStepper = ThreadStaticPools.RentExponentStepperCpu(divisorData);
ExponentRemainderStepperCpu powStepper = orderFactors.ExponentRemainderStepper;
powStepper.Reset();
bool powStepperInitialized = false;
int index = 0;
Span<ulong> stackGpuRemainders = stackalloc ulong[PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount];
Span<ulong> stackGpuRemainders = new(orderFactors.SpecialMaxBuffer, 0, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount);
ArrayPool<ulong> pool = ThreadStaticPools.UlongPool;
while (index < candidateCount && powUsed < powBudget)
{
@@ -819,9 +790,6 @@ internal static partial class PrimeOrderCalculator
pool.Return(gpuPool, clearArray: false);
}
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
result = candidate;
return true;
}
@@ -859,9 +827,6 @@ internal static partial class PrimeOrderCalculator
pool.Return(gpuPool, clearArray: false);
}
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
result = candidate;
return true;
}
@@ -874,9 +839,6 @@ internal static partial class PrimeOrderCalculator
index += batchSize;
}
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
// DebugLog("No candidate confirmed");
return false;
}
@@ -902,66 +864,75 @@ internal static partial class PrimeOrderCalculator
}
PartialFactorResult128 orderFactors = PartialFactorWide(order, config);
if (orderFactors.Factors is null)
if (orderFactors.Count == 0)
{
orderFactors.Dispose();
return false;
}
if (!orderFactors.FullyFactored)
try
{
if (orderFactors.Cofactor <= UInt128.One)
if (!orderFactors.FullyFactored)
{
if (orderFactors.Cofactor <= UInt128.One)
{
return false;
}
if (!IsPrimeWide(orderFactors.Cofactor))
{
return false;
}
orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
}
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity;
List<UInt128> candidates = orderFactors.FactorCandidatesList;
candidates.Clear();
FactorEntry128[] factorArray = orderFactors.Factors!;
BuildCandidatesWide(order, factorArray, orderFactors.Count, candidates, capacity);
if (candidates.Count == 0)
{
return false;
}
if (!IsPrimeWide(orderFactors.Cofactor))
SortCandidatesWide(prime, previousOrder, candidates);
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
int powUsed = 0;
int candidateCount = candidates.Count;
for (int i = 0; i < candidateCount; i++)
{
return false;
if (powUsed >= powBudget && powBudget > 0)
{
break;
}
UInt128 candidate = candidates[i];
powUsed++;
if (Pow2ModWideGpu(gpu, candidate, prime, divisorData) != UInt128.One)
{
continue;
}
if (!TryConfirmCandidateWideGpu(gpu, prime, candidate, config, ref powUsed, powBudget, divisorData))
{
continue;
}
result = candidate;
return true;
}
orderFactors = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
}
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity;
List<UInt128> candidates = new(capacity);
FactorEntry128[] factorArray = orderFactors.Factors!;
BuildCandidatesWide(order, factorArray, orderFactors.Count, candidates, capacity);
if (candidates.Count == 0)
{
return false;
}
SortCandidatesWide(prime, previousOrder, candidates);
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
int powUsed = 0;
int candidateCount = candidates.Count;
for (int i = 0; i < candidateCount; i++)
finally
{
if (powUsed >= powBudget && powBudget > 0)
{
break;
}
UInt128 candidate = candidates[i];
powUsed++;
if (Pow2ModWideGpu(gpu, candidate, prime, divisorData) != UInt128.One)
{
continue;
}
if (!TryConfirmCandidateWideGpu(gpu, prime, candidate, config, ref powUsed, powBudget, divisorData))
{
continue;
}
result = candidate;
return true;
orderFactors.Dispose();
}
return false;
}
private static bool TryPollardRhoGpu(PrimeOrderCalculatorAccelerator gpu, ulong n, out ulong factor)
@@ -1077,4 +1048,3 @@ internal static partial class PrimeOrderCalculator
return true;
}
}