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
+656 -883

No files matched your search

@@ -7,7 +7,7 @@ namespace PerfectNumbers.Core;
/// Tracks successive Montgomery residues for a single divisor while scanning exponent candidates on the CPU path. /// Tracks successive Montgomery residues for a single divisor while scanning exponent candidates on the CPU path.
/// Callers are responsible for seeding the stepper once per divisor and then advancing exponents in ascending order. /// Callers are responsible for seeding the stepper once per divisor and then advancing exponents in ascending order.
/// </summary> /// </summary>
internal struct ExponentRemainderStepperCpu(in MontgomeryDivisorData divisor, ulong cycleLength = 0UL) public struct ExponentRemainderStepperCpu(in MontgomeryDivisorData divisor, ulong cycleLength = 0UL)
{ {
private readonly MontgomeryDivisorData _divisor = divisor; private readonly MontgomeryDivisorData _divisor = divisor;
private readonly ulong _modulus = divisor.Modulus; private readonly ulong _modulus = divisor.Modulus;
@@ -0,0 +1,47 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
internal sealed class PartialFactorPendingEntry(ulong value, bool knownComposite)
{
[ThreadStatic]
private static PartialFactorPendingEntry? _poolHead;
private PartialFactorPendingEntry? _next;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static PartialFactorPendingEntry Rent(ulong value, bool knownComposite)
{
var poolHead = _poolHead;
if (poolHead == null)
{
return new(value, knownComposite);
}
_poolHead = poolHead._next;
poolHead.Value = value;
poolHead.KnownComposite = knownComposite;
poolHead.HasKnownPrimality = knownComposite;
poolHead.IsPrime = false;
return poolHead;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Return(PartialFactorPendingEntry entry)
{
entry._next = _poolHead;
_poolHead = entry;
}
public ulong Value = value;
public bool KnownComposite = knownComposite;
public bool HasKnownPrimality = knownComposite;
public bool IsPrime = false;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void WithPrimality(bool isPrime)
{
KnownComposite = KnownComposite || !isPrime;
HasKnownPrimality = true;
IsPrime = isPrime;
}
}
+64 -109
View File
@@ -1,154 +1,109 @@
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core namespace PerfectNumbers.Core
{ {
public sealed class PartialFactorResult public sealed class PartialFactorResult
{ {
private const int ExponentHardLimit = 256;
[ThreadStatic] [ThreadStatic]
private static PartialFactorResult? s_poolHead; private static PartialFactorResult? s_poolHead;
public ulong[]? Factors; public static readonly PartialFactorResult Empty = new(MontgomeryDivisorData.Empty)
public int[]? Exponents;
private PartialFactorResult? _next;
public ulong Cofactor;
public bool FullyFactored;
public int Count;
public bool CofactorIsPrime;
private PartialFactorResult()
{
}
public static PartialFactorResult Rent(in ulong[]? factors, in int[]? exponents, ulong cofactor, bool fullyFactored, int count, bool cofactorIsPrime)
{
PartialFactorResult? instance = s_poolHead;
if (instance is null)
{
instance = new PartialFactorResult();
}
else
{
s_poolHead = instance._next;
}
instance._next = null;
instance.Factors = factors;
instance.Exponents = exponents;
instance.Cofactor = cofactor;
instance.FullyFactored = fullyFactored;
instance.Count = count;
instance.CofactorIsPrime = cofactorIsPrime;
return instance;
}
public static PartialFactorResult Rent(ulong cofactor, bool fullyFactored, int count, bool cofactorIsPrime)
{
PartialFactorResult? instance = s_poolHead;
if (instance is null)
{
instance = new PartialFactorResult();
}
else
{
s_poolHead = instance._next;
}
instance._next = null;
instance.Factors = null;
instance.Exponents = null;
instance.Cofactor = cofactor;
instance.FullyFactored = fullyFactored;
instance.Count = count;
instance.CofactorIsPrime = cofactorIsPrime;
return instance;
}
public static readonly PartialFactorResult Empty = new()
{ {
Cofactor = 1UL, Cofactor = 1UL,
FullyFactored = true FullyFactored = true
}; };
internal readonly List<PartialFactorPendingEntry> Pending = new(PrimeOrderConstants.GpuSmallPrimeFactorSlots);
internal readonly FixedCapacityStack<ulong> CompositeStack = new(PrimeOrderConstants.GpuSmallPrimeFactorSlots);
internal readonly ulong[] SpecialMaxBuffer = new ulong[PrimeOrderConstants.GpuSmallPrimeFactorSlots];
public readonly ulong[] Factors = new ulong[ExponentHardLimit];
public readonly int[] Exponents = new int[ExponentHardLimit];
public readonly ulong[] FactorCandidates = new ulong[ExponentHardLimit];
public readonly List<ulong> FactorCandidatesList = new(ExponentHardLimit);
public readonly ulong[] ValidateOrderFactorCandidates = new ulong[ExponentHardLimit];
public readonly bool[] FactorCandidateEvaluations = new bool[ExponentHardLimit];
internal readonly Dictionary<ulong, int> FactorCounts = new(capacity: 8);
public ExponentRemainderStepperCpu ExponentRemainderStepper;
private PartialFactorResult? _next;
public ulong Cofactor;
public bool CofactorIsPrime;
public int Count;
public bool FullyFactored;
public bool HasFactors;
private PartialFactorResult(in MontgomeryDivisorData divisorData)
{
ExponentRemainderStepper = new(divisorData);
}
public static PartialFactorResult Rent(in MontgomeryDivisorData divisorData)
{
if (s_poolHead is { } instance)
{
s_poolHead = instance._next;
instance._next = null;
instance.Cofactor = 0UL;
instance.FullyFactored = false;
instance.Count = 0;
instance.CofactorIsPrime = false;
instance.HasFactors = false;
if (!instance.ExponentRemainderStepper.MatchesDivisor(divisorData, 0UL))
{
instance.ExponentRemainderStepper = new ExponentRemainderStepperCpu(divisorData, 0UL);
}
return instance;
}
return new PartialFactorResult(divisorData);
}
public void WithAdditionalPrime(ulong prime) public void WithAdditionalPrime(ulong prime)
{ {
ulong[]? sourceFactors = Factors; ulong[] sourceFactors = Factors;
int sourceCount = Count; int sourceCount = Count;
if (sourceFactors is null || sourceCount == 0) if (sourceCount == 0)
{ {
InitializeWithPrime(prime); InitializeWithPrime(prime);
return; return;
} }
int newSize = (int)BitOperations.RoundUpToPowerOf2((uint)sourceCount + 1U); int[] sourceExponents = Exponents;
ulong[] extendedFactors = sourceFactors.Length >= newSize
? sourceFactors
: ResizeFactors(sourceFactors, sourceCount, newSize);
int[] sourceExponents = Exponents!; int newSize = sourceCount + 1;
int[] extendedExponents = sourceExponents.Length >= newSize sourceFactors[sourceCount] = prime;
? sourceExponents sourceExponents[sourceCount] = 1;
: ResizeExponents(sourceCount, newSize, sourceExponents); Array.Sort(sourceFactors, sourceExponents, 0, newSize);
newSize = sourceCount + 1;
extendedFactors[sourceCount] = prime;
extendedExponents[sourceCount] = 1;
Array.Sort(extendedFactors, extendedExponents, 0, newSize);
Factors = extendedFactors;
Exponents = extendedExponents;
Cofactor = 1UL; Cofactor = 1UL;
FullyFactored = true; FullyFactored = true;
Count = newSize; Count = newSize;
CofactorIsPrime = false; CofactorIsPrime = false;
} HasFactors = true;
private static int[] ResizeExponents(int sourceCount, int newSize, in int[] sourceExponents)
{
int[] extendedExponents = FixedCapacityPools.ExclusiveIntArray.Rent(newSize);
Array.Copy(sourceExponents, 0, extendedExponents, 0, sourceCount);
FixedCapacityPools.ExclusiveIntArray.Return(sourceExponents);
return extendedExponents;
}
private static ulong[] ResizeFactors(in ulong[] sourceFactors, int sourceCount, int newSize)
{
ulong[] extendedFactors = FixedCapacityPools.ExclusiveUlongArray.Rent(newSize);
Array.Copy(sourceFactors, 0, extendedFactors, 0, sourceCount);
FixedCapacityPools.ExclusiveUlongArray.Return(sourceFactors);
return extendedFactors;
} }
private void InitializeWithPrime(ulong prime) private void InitializeWithPrime(ulong prime)
{ {
ulong[] factors = FixedCapacityPools.ExclusiveUlongArray.Rent(1); Factors[0] = prime;
int[] exponents = FixedCapacityPools.ExclusiveIntArray.Rent(1); Exponents[0] = 1;
factors[0] = prime;
exponents[0] = 1;
Factors = factors;
Exponents = exponents;
Cofactor = 1UL; Cofactor = 1UL;
FullyFactored = true; FullyFactored = true;
Count = 1; Count = 1;
CofactorIsPrime = false; CofactorIsPrime = false;
HasFactors = true;
} }
public void Dispose() public void Dispose()
{ {
if (this != Empty) if (this != Empty)
{ {
ulong[]? factors = Factors;
if (factors is not null)
{
int[] exponents = Exponents!;
Factors = null;
Exponents = null;
FixedCapacityPools.ExclusiveUlongArray.Return(factors);
FixedCapacityPools.ExclusiveIntArray.Return(exponents);
}
_next = s_poolHead; _next = s_poolHead;
s_poolHead = this; s_poolHead = this;
} }
File diff suppressed because it is too large. Load diff
+80 -110
View File
@@ -76,12 +76,12 @@ internal static partial class PrimeOrderCalculator
return gpuOrder; return gpuOrder;
} }
PartialFactorResult phiFactors = PartialFactorGpu(gpu, phi, config); PartialFactorResult phiFactors = PartialFactorGpu(gpu, divisorData, phi, config);
ulong result; ulong result;
if (phiFactors.Factors is null) if (phiFactors.Count == 0)
{ {
result = CalculateByFactorizationCpu(prime, divisorData); result = CalculateByFactorizationCpu(prime, divisorData, phiFactors);
phiFactors.Dispose(); phiFactors.Dispose();
return result; return result;
@@ -147,8 +147,10 @@ internal static partial class PrimeOrderCalculator
UInt128 phi = prime - UInt128.One; UInt128 phi = prime - UInt128.One;
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config); PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
try
{
UInt128 result; UInt128 result;
if (phiFactors.Factors is null) if (phiFactors.Count == 0)
{ {
result = FinishStrictlyWideGpu(gpu, prime, divisorData); result = FinishStrictlyWideGpu(gpu, prime, divisorData);
} }
@@ -159,9 +161,14 @@ internal static partial class PrimeOrderCalculator
return result; return result;
} }
finally
{
phiFactors.Dispose();
}
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [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) while (compositeStack.Count > 0)
{ {
@@ -170,7 +177,7 @@ internal static partial class PrimeOrderCalculator
if (isPrime) if (isPrime)
{ {
AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite);
continue; continue;
} }
@@ -178,14 +185,14 @@ internal static partial class PrimeOrderCalculator
if (currentTimestamp > deadlineTimestamp) if (currentTimestamp > deadlineTimestamp)
{ {
pollardRhoDeadlineReached = true; pollardRhoDeadlineReached = true;
pending.Add(new PendingEntry(composite, knownComposite: true)); pending.Add(new PartialFactorPendingEntry(composite, knownComposite: true));
continue; continue;
} }
// if (!TryPollardRhoGpu(gpu, composite, out ulong factor)) // if (!TryPollardRhoGpu(gpu, composite, out ulong factor))
if (!TryPollardRhoCpu(composite, deadlineTimestamp, 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; continue;
} }
@@ -200,7 +207,6 @@ internal static partial class PrimeOrderCalculator
PrimeOrderCalculatorAccelerator gpu, PrimeOrderCalculatorAccelerator gpu,
ReadOnlySpan<ulong> factors, ReadOnlySpan<ulong> factors,
ulong phi, ulong phi,
ulong prime,
in MontgomeryDivisorData divisorData) in MontgomeryDivisorData divisorData)
{ {
int factorCount = factors.Length; 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) 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; ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
int length = factors.Count; int length = factors.Count;
// TODO: This should never trigger from production code - check // TODO: This should never trigger from production code - check
@@ -243,9 +248,7 @@ internal static partial class PrimeOrderCalculator
// { // {
// return order; // return order;
// } // }
FactorEntry128[] tempArray = pool.Rent(length + 1); Span<FactorEntry128> buffer = factors.TempFactors.AsSpan(0, length + 1);
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
factorSpan.CopyTo(buffer); factorSpan.CopyTo(buffer);
if (!factors.FullyFactored && factors.Cofactor > UInt128.One && IsPrimeWide(factors.Cofactor)) if (!factors.FullyFactored && factors.Cofactor > UInt128.One && IsPrimeWide(factors.Cofactor))
@@ -263,7 +266,7 @@ internal static partial class PrimeOrderCalculator
int exponent = buffer[i].Exponent; int exponent = buffer[i].Exponent;
for (int iteration = 0; iteration < exponent; iteration++) for (int iteration = 0; iteration < exponent; iteration++)
{ {
if ((order % primeFactor) == UInt128.Zero) if (order.ReduceCycleRemainder(primeFactor) == UInt128.Zero)
{ {
UInt128 reduced = order / primeFactor; UInt128 reduced = order / primeFactor;
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One) if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
@@ -277,7 +280,6 @@ internal static partial class PrimeOrderCalculator
} }
} }
pool.Return(tempArray, clearArray: false);
return order; return order;
} }
@@ -302,7 +304,7 @@ internal static partial class PrimeOrderCalculator
int exponent = entries[i].Value; int exponent = entries[i].Value;
for (int iteration = 0; iteration < exponent; iteration++) for (int iteration = 0; iteration < exponent; iteration++)
{ {
if ((order % primeFactor) != UInt128.Zero) if (order.ReduceCycleRemainder(primeFactor) != UInt128.Zero)
{ {
UInt128 candidate = order / primeFactor; UInt128 candidate = order / primeFactor;
if (Pow2ModWideGpu(gpu, candidate, prime, divisorData) == UInt128.One) if (Pow2ModWideGpu(gpu, candidate, prime, divisorData) == UInt128.One)
@@ -337,7 +339,7 @@ internal static partial class PrimeOrderCalculator
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [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) if (value <= 1UL)
{ {
@@ -345,20 +347,18 @@ internal static partial class PrimeOrderCalculator
} }
// stackalloc is faster than pooling // stackalloc is faster than pooling
Span<ulong> primeSlots = stackalloc ulong[PrimeOrderConstants.GpuSmallPrimeFactorSlots]; PartialFactorResult result = PartialFactorResult.Rent(divisorData);
Span<int> exponentSlots = stackalloc int[PrimeOrderConstants.GpuSmallPrimeFactorSlots]; Span<ulong> primeSlots = new(result.Factors);
ulong[] primeSlotArray = []; Span<int> exponentSlots = new (result.Exponents);
int[] exponentSlotArray = [];
// 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();
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit; uint limit = config.SmallFactorLimit;
ulong remaining = value; 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<PartialFactorPendingEntry> pending = result.Pending;
pending.Clear();
List<PendingEntry> pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(2);
if (!gpuFactored) if (!gpuFactored)
{ {
@@ -371,31 +371,28 @@ internal static partial class PrimeOrderCalculator
out remaining); out remaining);
} }
bool limitReached;
if (remaining > 1UL) if (remaining > 1UL)
{ {
if (config.PollardRhoMilliseconds > 0) if (config.PollardRhoMilliseconds > 0)
{ {
long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds); long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds);
FixedCapacityStack<ulong> compositeStack = ThreadStaticPools.RentUlongStack(4); FixedCapacityStack<ulong> compositeStack = result.CompositeStack;
compositeStack.Clear();
compositeStack.Push(remaining); 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) if (limitReached)
{ {
while (compositeStack.Count > 0) 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 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; int index = 0;
for (; index < pendingCount; index++) for (; index < pendingCount; index++)
{ {
PendingEntry entry = pending[index]; PartialFactorPendingEntry entry = pending[index];
ulong composite = entry.Value; ulong composite = entry.Value;
if (entry.KnownComposite) if (entry.KnownComposite)
{ {
cofactor = checked(cofactor * composite); cofactor = checked(cofactor * composite);
cofactorContainsComposite = true; cofactorContainsComposite = true;
PartialFactorPendingEntry.Return(entry);
continue; continue;
} }
@@ -425,7 +423,7 @@ internal static partial class PrimeOrderCalculator
if (entry.IsPrime) if (entry.IsPrime)
{ {
AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite);
} }
// composite is never smaller on the execution path // composite is never smaller on the execution path
// else if (composite > 1UL) // else if (composite > 1UL)
@@ -434,6 +432,8 @@ internal static partial class PrimeOrderCalculator
cofactor = checked(cofactor * composite); cofactor = checked(cofactor * composite);
cofactorContainsComposite = true; cofactorContainsComposite = true;
} }
PartialFactorPendingEntry.Return(entry);
} }
bool cofactorIsPrime; bool cofactorIsPrime;
@@ -450,19 +450,6 @@ internal static partial class PrimeOrderCalculator
cofactorIsPrime = PrimeTesterByLastDigit.IsPrimeCpu(cofactor); 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 // This will never happen in production code. We'll always get at least 1 factor
// if (factorCount == 0) // if (factorCount == 0)
// { // {
@@ -476,41 +463,13 @@ internal static partial class PrimeOrderCalculator
// goto ReturnResult; // goto ReturnResult;
// } // }
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool; Array.Sort(result.Factors, result.Exponents, 0, factorCount);
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);
// Check if it was factored completely // Check if it was factored completely
limitReached = cofactor == 1UL; result.Cofactor = cofactor;
result.FullyFactored = cofactor == 1UL;
result = PartialFactorResult.Rent(factorsArray, exponentsArray, cofactor, limitReached, factorCount, cofactorIsPrime); result.Count = factorCount;
result.CofactorIsPrime = cofactorIsPrime;
ReturnResult:
if (primeSlotArray.Length > 0)
{
ThreadStaticPools.UlongPool.Return(primeSlotArray, clearArray: false);
ThreadStaticPools.IntPool.Return(exponentSlotArray!, clearArray: false);
}
pending.Clear();
ThreadStaticPools.ReturnPrimeOrderPendingEntryList(pending);
return result; 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) 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); PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
if (factorization.Factors is null) if (factorization.Count == 0)
{ {
factorization.Dispose();
return false; return false;
} }
@@ -589,26 +549,30 @@ internal static partial class PrimeOrderCalculator
{ {
if (factorization.Cofactor <= UInt128.One) if (factorization.Cofactor <= UInt128.One)
{ {
factorization.Dispose();
return false; return false;
} }
if (!IsPrimeWide(factorization.Cofactor)) if (!IsPrimeWide(factorization.Cofactor))
{ {
factorization.Dispose();
return false; return false;
} }
factorization = factorization.WithAdditionalPrime(factorization.Cofactor); factorization.WithAdditionalPrime(factorization.Cofactor);
} }
ReadOnlySpan<FactorEntry128> span = factorization.Factors; ReadOnlySpan<FactorEntry128> span = factorization.Factors;
int length = factorization.Count; int length = factorization.Count;
try
{
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
UInt128 primeFactor = span[i].Value; UInt128 primeFactor = span[i].Value;
UInt128 reduced = candidate; UInt128 reduced = candidate;
for (int iteration = 0; iteration < span[i].Exponent; iteration++) for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{ {
if ((reduced % primeFactor) != UInt128.Zero) if (reduced.ReduceCycleRemainder(primeFactor) != UInt128.Zero)
{ {
break; break;
} }
@@ -629,6 +593,11 @@ internal static partial class PrimeOrderCalculator
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) 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); PartialFactorResult128 factorization = PartialFactorWide(order, config);
if (factorization.Factors is null) if (factorization.Count == 0)
{ {
factorization.Dispose();
return false; return false;
} }
@@ -652,15 +622,17 @@ internal static partial class PrimeOrderCalculator
{ {
if (factorization.Cofactor <= UInt128.One) if (factorization.Cofactor <= UInt128.One)
{ {
factorization.Dispose();
return false; return false;
} }
if (!IsPrimeWide(factorization.Cofactor)) if (!IsPrimeWide(factorization.Cofactor))
{ {
factorization.Dispose();
return false; return false;
} }
factorization = factorization.WithAdditionalPrime(factorization.Cofactor); factorization.WithAdditionalPrime(factorization.Cofactor);
} }
ReadOnlySpan<FactorEntry128> span = factorization.Factors; ReadOnlySpan<FactorEntry128> span = factorization.Factors;
@@ -679,11 +651,13 @@ internal static partial class PrimeOrderCalculator
reduced /= primeFactor; reduced /= primeFactor;
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One) if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
{ {
factorization.Dispose();
return false; return false;
} }
} }
} }
factorization.Dispose();
return true; return true;
} }
@@ -695,7 +669,6 @@ internal static partial class PrimeOrderCalculator
ulong? previousOrder, ulong? previousOrder,
in MontgomeryDivisorData divisorData, in MontgomeryDivisorData divisorData,
in PrimeOrderCalculatorConfig config, in PrimeOrderCalculatorConfig config,
PartialFactorResult phiFactors,
PartialFactorResult? cachedOrderFactors, PartialFactorResult? cachedOrderFactors,
out ulong result) out ulong result)
{ {
@@ -707,10 +680,10 @@ internal static partial class PrimeOrderCalculator
} }
// Reuse the partial factorization from TryConfirmOrderCpu when available. // Reuse the partial factorization from TryConfirmOrderCpu when available.
PartialFactorResult orderFactors = cachedOrderFactors ?? PartialFactorGpu(gpu, order, config); PartialFactorResult orderFactors = cachedOrderFactors ?? PartialFactorGpu(gpu, divisorData, order, config);
try try
{ {
if (orderFactors.Factors is null) if (orderFactors.Count == 0)
{ {
return false; return false;
} }
@@ -726,16 +699,13 @@ internal static partial class PrimeOrderCalculator
} }
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity; int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity;
List<ulong> candidates = ThreadStaticPools.RentUlongList(capacity); List<ulong> candidates = orderFactors.FactorCandidatesList;
candidates.Clear(); 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; int candidateCount = candidates.Count;
if (candidateCount == 0) if (candidateCount == 0)
{ {
ThreadStaticPools.ReturnUlongList(candidates);
return false; return false;
} }
@@ -745,11 +715,12 @@ internal static partial class PrimeOrderCalculator
int powUsed = 0; int powUsed = 0;
bool allowGpuBatch = true; bool allowGpuBatch = true;
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates); Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
ExponentRemainderStepperCpu powStepper = ThreadStaticPools.RentExponentStepperCpu(divisorData); ExponentRemainderStepperCpu powStepper = orderFactors.ExponentRemainderStepper;
powStepper.Reset();
bool powStepperInitialized = false; bool powStepperInitialized = false;
int index = 0; int index = 0;
Span<ulong> stackGpuRemainders = stackalloc ulong[PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount]; Span<ulong> stackGpuRemainders = new(orderFactors.SpecialMaxBuffer, 0, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount);
ArrayPool<ulong> pool = ThreadStaticPools.UlongPool; ArrayPool<ulong> pool = ThreadStaticPools.UlongPool;
while (index < candidateCount && powUsed < powBudget) while (index < candidateCount && powUsed < powBudget)
{ {
@@ -819,9 +790,6 @@ internal static partial class PrimeOrderCalculator
pool.Return(gpuPool, clearArray: false); pool.Return(gpuPool, clearArray: false);
} }
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
result = candidate; result = candidate;
return true; return true;
} }
@@ -859,9 +827,6 @@ internal static partial class PrimeOrderCalculator
pool.Return(gpuPool, clearArray: false); pool.Return(gpuPool, clearArray: false);
} }
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
result = candidate; result = candidate;
return true; return true;
} }
@@ -874,9 +839,6 @@ internal static partial class PrimeOrderCalculator
index += batchSize; index += batchSize;
} }
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
// DebugLog("No candidate confirmed"); // DebugLog("No candidate confirmed");
return false; return false;
} }
@@ -902,11 +864,14 @@ internal static partial class PrimeOrderCalculator
} }
PartialFactorResult128 orderFactors = PartialFactorWide(order, config); PartialFactorResult128 orderFactors = PartialFactorWide(order, config);
if (orderFactors.Factors is null) if (orderFactors.Count == 0)
{ {
orderFactors.Dispose();
return false; return false;
} }
try
{
if (!orderFactors.FullyFactored) if (!orderFactors.FullyFactored)
{ {
if (orderFactors.Cofactor <= UInt128.One) if (orderFactors.Cofactor <= UInt128.One)
@@ -919,11 +884,12 @@ internal static partial class PrimeOrderCalculator
return false; return false;
} }
orderFactors = orderFactors.WithAdditionalPrime(orderFactors.Cofactor); orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
} }
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity; int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity;
List<UInt128> candidates = new(capacity); List<UInt128> candidates = orderFactors.FactorCandidatesList;
candidates.Clear();
FactorEntry128[] factorArray = orderFactors.Factors!; FactorEntry128[] factorArray = orderFactors.Factors!;
BuildCandidatesWide(order, factorArray, orderFactors.Count, candidates, capacity); BuildCandidatesWide(order, factorArray, orderFactors.Count, candidates, capacity);
if (candidates.Count == 0) if (candidates.Count == 0)
@@ -963,6 +929,11 @@ internal static partial class PrimeOrderCalculator
return false; return false;
} }
finally
{
orderFactors.Dispose();
}
}
private static bool TryPollardRhoGpu(PrimeOrderCalculatorAccelerator gpu, ulong n, out ulong factor) private static bool TryPollardRhoGpu(PrimeOrderCalculatorAccelerator gpu, ulong n, out ulong factor)
{ {
@@ -1077,4 +1048,3 @@ internal static partial class PrimeOrderCalculator
return true; return true;
} }
} }
@@ -1,8 +1,6 @@
using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using ILGPU;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.Gpu.Accelerators;
@@ -25,12 +23,12 @@ internal static partial class PrimeOrderCalculator
ulong phi = prime - 1UL; ulong phi = prime - 1UL;
PartialFactorResult phiFactors = PartialFactorHybrid(gpu, phi, config); PartialFactorResult phiFactors = PartialFactorHybrid(gpu, divisorData, phi, config);
ulong result; ulong result;
if (phiFactors.Factors is null) if (phiFactors.Count == 0)
{ {
result = CalculateByFactorizationCpu(prime, divisorData); result = CalculateByFactorizationCpu(prime, divisorData, phiFactors);
phiFactors.Dispose(); phiFactors.Dispose();
return result; return result;
@@ -75,15 +73,14 @@ internal static partial class PrimeOrderCalculator
} }
else else
{ {
divisorData = MontgomeryDivisorData.Empty; result = CalculateWideInternalCpu(prime, previousOrder, config);
result = CalculateWideInternalCpu(prime, previousOrder, divisorData, config);
} }
return result; return result;
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static void CollectFactorsHybrid(PrimeOrderCalculatorAccelerator gpu, Span<ulong> primeSlots, Span<int> exponentSlots, ref int factorCount, List<PendingEntry> pending, FixedCapacityStack<ulong> compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) private static void CollectFactorsHybrid(PrimeOrderCalculatorAccelerator gpu, Span<ulong> primeSlots, Span<int> exponentSlots, ref int factorCount, List<PartialFactorPendingEntry> pending, FixedCapacityStack<ulong> compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached)
{ {
while (compositeStack.Count > 0) while (compositeStack.Count > 0)
{ {
@@ -92,20 +89,20 @@ internal static partial class PrimeOrderCalculator
if (isPrime) if (isPrime)
{ {
AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite);
continue; continue;
} }
pollardRhoDeadlineReached = Stopwatch.GetTimestamp() > deadlineTimestamp; pollardRhoDeadlineReached = Stopwatch.GetTimestamp() > deadlineTimestamp;
if (pollardRhoDeadlineReached) if (pollardRhoDeadlineReached)
{ {
pending.Add(new PendingEntry(composite, knownComposite: true)); pending.Add(new PartialFactorPendingEntry(composite, knownComposite: true));
continue; continue;
} }
if (!TryPollardRhoCpu(composite, deadlineTimestamp, 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; continue;
} }
@@ -134,7 +131,7 @@ internal static partial class PrimeOrderCalculator
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static PartialFactorResult PartialFactorHybrid(PrimeOrderCalculatorAccelerator gpu, ulong value, in PrimeOrderCalculatorConfig config) private static PartialFactorResult PartialFactorHybrid(PrimeOrderCalculatorAccelerator gpu, in MontgomeryDivisorData divisorData, ulong value, in PrimeOrderCalculatorConfig config)
{ {
if (value <= 1UL) if (value <= 1UL)
{ {
@@ -142,18 +139,17 @@ internal static partial class PrimeOrderCalculator
} }
// stackalloc is faster than pooling // stackalloc is faster than pooling
Span<ulong> primeSlots = stackalloc ulong[PrimeOrderConstants.GpuSmallPrimeFactorSlots]; PartialFactorResult result = PartialFactorResult.Rent(divisorData);
Span<int> exponentSlots = stackalloc int[PrimeOrderConstants.GpuSmallPrimeFactorSlots]; Span<ulong> primeSlots = new (result.Factors);
ulong[] primeSlotArray = []; Span<int> exponentSlots = new (result.Exponents);
int[] exponentSlotArray = [];
// 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();
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit; uint limit = config.SmallFactorLimit;
ulong remaining = value;
List<PendingEntry> pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(2); List<PartialFactorPendingEntry> pending = result.Pending;
pending.Clear();
PopulateSmallPrimeFactorsCpu( PopulateSmallPrimeFactorsCpu(
value, value,
@@ -161,33 +157,30 @@ internal static partial class PrimeOrderCalculator
primeSlots, primeSlots,
exponentSlots, exponentSlots,
out int factorCount, out int factorCount,
out remaining); out ulong remaining);
bool limitReached;
if (remaining > 1UL) if (remaining > 1UL)
{ {
if (config.PollardRhoMilliseconds > 0) if (config.PollardRhoMilliseconds > 0)
{ {
long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds); long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds);
FixedCapacityStack<ulong> compositeStack = ThreadStaticPools.RentUlongStack(4); FixedCapacityStack<ulong> compositeStack = result.CompositeStack;
compositeStack.Clear();
compositeStack.Push(remaining); compositeStack.Push(remaining);
CollectFactorsHybrid(gpu, primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); CollectFactorsHybrid(gpu, primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out bool limitReached);
if (limitReached) if (limitReached)
{ {
while (compositeStack.Count > 0) 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 else
{ {
pending.Add(new PendingEntry(remaining, knownComposite: false)); pending.Add(PartialFactorPendingEntry.Rent(remaining, knownComposite: false));
} }
} }
@@ -197,13 +190,14 @@ internal static partial class PrimeOrderCalculator
int index = 0; int index = 0;
for (; index < pendingCount; index++) for (; index < pendingCount; index++)
{ {
PendingEntry entry = pending[index]; PartialFactorPendingEntry entry = pending[index];
ulong composite = entry.Value; ulong composite = entry.Value;
if (entry.KnownComposite) if (entry.KnownComposite)
{ {
cofactor = checked(cofactor * composite); cofactor = checked(cofactor * composite);
cofactorContainsComposite = true; cofactorContainsComposite = true;
PartialFactorPendingEntry.Return(entry);
continue; continue;
} }
@@ -217,7 +211,7 @@ internal static partial class PrimeOrderCalculator
if (entry.IsPrime) if (entry.IsPrime)
{ {
AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite);
} }
// composite is never smaller on the execution path // composite is never smaller on the execution path
// else if (composite > 1UL) // else if (composite > 1UL)
@@ -226,6 +220,8 @@ internal static partial class PrimeOrderCalculator
cofactor = checked(cofactor * composite); cofactor = checked(cofactor * composite);
cofactorContainsComposite = true; cofactorContainsComposite = true;
} }
PartialFactorPendingEntry.Return(entry);
} }
bool cofactorIsPrime; bool cofactorIsPrime;
@@ -242,20 +238,16 @@ internal static partial class PrimeOrderCalculator
cofactorIsPrime = RunOnCpu() ? PrimeTesterByLastDigit.IsPrimeCpu(cofactor) : HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, cofactor); cofactorIsPrime = RunOnCpu() ? PrimeTesterByLastDigit.IsPrimeCpu(cofactor) : HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, 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 // This will never happen in production code. We'll always get at least 1 factor
// if (factorCount == 0)
// {
// result.Cofactor = cofactor;
// result.FullyFactored = cofactor != value && cofactor == 1UL;
// result.Count = 0;
// result.CofactorIsPrime = cofactorIsPrime;
// goto ReturnResult;
// }
// if (factorCount == 0) // if (factorCount == 0)
// { // {
// if (cofactor == value) // if (cofactor == value)
@@ -268,41 +260,13 @@ internal static partial class PrimeOrderCalculator
// goto ReturnResult; // goto ReturnResult;
// } // }
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool; Array.Sort(result.Factors, result.Exponents, 0, factorCount);
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);
// Check if it was factored completely // Check if it was factored completely
limitReached = cofactor == 1UL; result.Cofactor = cofactor;
result.FullyFactored = cofactor == 1UL;
result = PartialFactorResult.Rent(factorsArray, exponentsArray, cofactor, limitReached, factorCount, cofactorIsPrime); result.Count = factorCount;
result.CofactorIsPrime = cofactorIsPrime;
ReturnResult:
if (primeSlotArray.Length > 0)
{
ThreadStaticPools.UlongPool.Return(primeSlotArray, clearArray: false);
ThreadStaticPools.IntPool.Return(exponentSlotArray!, clearArray: false);
}
pending.Clear();
ThreadStaticPools.ReturnPrimeOrderPendingEntryList(pending);
return result; return result;
} }
@@ -336,13 +300,13 @@ internal static partial class PrimeOrderCalculator
ulong phi, ulong phi,
PartialFactorResult phiFactors) PartialFactorResult phiFactors)
{ {
if (phiFactors.FullyFactored && TrySpecialMaxHybrid(gpu, phi, prime, phiFactors, divisorData)) if (phiFactors.FullyFactored && TrySpecialMaxHybrid(gpu, phi, phiFactors, divisorData))
{ {
return phi; return phi;
} }
ulong candidateOrder = InitializeStartingOrderHybrid(gpu, prime, phi, divisorData); ulong candidateOrder = InitializeStartingOrderHybrid(gpu, prime, phi, divisorData);
candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData); candidateOrder = ExponentLoweringCpu(candidateOrder, phiFactors, divisorData);
if (TryConfirmOrderHybrid(gpu, prime, candidateOrder, divisorData, config, out PartialFactorResult? orderFactors)) if (TryConfirmOrderHybrid(gpu, prime, candidateOrder, divisorData, config, out PartialFactorResult? orderFactors))
{ {
@@ -352,10 +316,10 @@ internal static partial class PrimeOrderCalculator
if (config.StrictMode) if (config.StrictMode)
{ {
orderFactors?.Dispose(); orderFactors?.Dispose();
return CalculateByFactorizationCpu(prime, divisorData); return CalculateByFactorizationCpu(prime, divisorData, phiFactors);
} }
if (TryHeuristicFinishHybrid(gpu, prime, candidateOrder, previousOrder, divisorData, config, phiFactors, orderFactors, out ulong order)) if (TryHeuristicFinishHybrid(gpu, prime, candidateOrder, previousOrder, divisorData, config, orderFactors, out ulong order))
{ {
return order; return order;
} }
@@ -407,9 +371,9 @@ internal static partial class PrimeOrderCalculator
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static bool TryConfirmCandidateHybrid(PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderCalculatorConfig config, ref int powUsed, int powBudget) private static bool TryConfirmCandidateHybrid(PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderCalculatorConfig config, ref int powUsed, int powBudget)
{ {
PartialFactorResult factorization = PartialFactorHybrid(gpu, candidate, config); PartialFactorResult factorization = PartialFactorHybrid(gpu, divisorData, candidate, config);
if (factorization.Factors is null) if (factorization.Count == 0)
{ {
factorization.Dispose(); factorization.Dispose();
return false; return false;
@@ -430,12 +394,8 @@ internal static partial class PrimeOrderCalculator
ReadOnlySpan<int> exponentsSpan = factorization.Exponents; ReadOnlySpan<int> exponentsSpan = factorization.Exponents;
int length = factorization.Count; int length = factorization.Count;
const int StackExponentCapacity = 32; Span<ulong> stackBuffer = new(factorization.SpecialMaxBuffer);
const int ExponentHardLimit = 256; ExponentRemainderStepperCpu stepper = factorization.ExponentRemainderStepper;
Span<ulong> stackBuffer = stackalloc ulong[StackExponentCapacity];
ulong[]? heapCandidateArray = null;
ExponentRemainderStepperCpu stepper = ThreadStaticPools.RentExponentStepperCpu(divisorData);
bool violates = false; bool violates = false;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
@@ -449,35 +409,13 @@ internal static partial class PrimeOrderCalculator
// continue; // continue;
// } // }
if (exponent > ExponentHardLimit)
{
throw new InvalidOperationException($"Candidate factor exponent {exponent} exceeds the supported limit of {ExponentHardLimit}.");
}
if (exponent <= StackExponentCapacity)
{
if (CheckCandidateViolation(stackBuffer[..exponent], primeFactor, exponent, candidate, prime, ref powUsed, powBudget, ref stepper)) if (CheckCandidateViolation(stackBuffer[..exponent], primeFactor, exponent, candidate, prime, ref powUsed, powBudget, ref stepper))
{ {
violates = true; violates = true;
break; break;
} }
continue;
} }
heapCandidateArray ??= ThreadStaticPools.UlongPool.Rent(ExponentHardLimit);
if (CheckCandidateViolation(heapCandidateArray.AsSpan(0, exponent), primeFactor, exponent, candidate, prime, ref powUsed, powBudget, ref stepper))
{
violates = true;
break;
}
}
ThreadStaticPools.ReturnExponentStepperCpu(stepper);
if (heapCandidateArray is not null)
{
ThreadStaticPools.UlongPool.Return(heapCandidateArray);
}
factorization.Dispose(); factorization.Dispose();
return !violates; return !violates;
@@ -504,8 +442,8 @@ internal static partial class PrimeOrderCalculator
return false; return false;
} }
PartialFactorResult factorization = PartialFactorHybrid(gpu, order, config); PartialFactorResult factorization = PartialFactorHybrid(gpu, divisorData, order, config);
if (factorization.Factors is null) if (factorization.Count == 0)
{ {
factorization.Dispose(); factorization.Dispose();
return false; return false;
@@ -522,7 +460,7 @@ internal static partial class PrimeOrderCalculator
factorization.WithAdditionalPrime(factorization.Cofactor); factorization.WithAdditionalPrime(factorization.Cofactor);
} }
if (!ValidateOrderAgainstFactors(prime, order, divisorData, factorization)) if (!ValidateOrderAgainstFactors(order, factorization))
{ {
reusableFactorization = factorization; reusableFactorization = factorization;
return false; return false;
@@ -540,7 +478,6 @@ internal static partial class PrimeOrderCalculator
ulong? previousOrder, ulong? previousOrder,
in MontgomeryDivisorData divisorData, in MontgomeryDivisorData divisorData,
in PrimeOrderCalculatorConfig config, in PrimeOrderCalculatorConfig config,
PartialFactorResult phiFactors,
PartialFactorResult? cachedOrderFactors, PartialFactorResult? cachedOrderFactors,
out ulong result) out ulong result)
{ {
@@ -552,10 +489,10 @@ internal static partial class PrimeOrderCalculator
} }
// Reuse the partial factorization from TryConfirmOrderCpu when available. // Reuse the partial factorization from TryConfirmOrderCpu when available.
PartialFactorResult orderFactors = cachedOrderFactors ?? PartialFactorHybrid(gpu, order, config); PartialFactorResult orderFactors = cachedOrderFactors ?? PartialFactorHybrid(gpu, divisorData, order, config);
try try
{ {
if (orderFactors.Factors is null) if (orderFactors.Count == 0)
{ {
return false; return false;
} }
@@ -571,16 +508,13 @@ internal static partial class PrimeOrderCalculator
} }
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity; int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity;
List<ulong> candidates = ThreadStaticPools.RentUlongList(capacity); List<ulong> candidates = orderFactors.FactorCandidatesList;
candidates.Clear(); 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; int candidateCount = candidates.Count;
if (candidateCount == 0) if (candidateCount == 0)
{ {
ThreadStaticPools.ReturnUlongList(candidates);
return false; return false;
} }
@@ -589,50 +523,32 @@ internal static partial class PrimeOrderCalculator
int powBudget = config.MaxPowChecks <= 0 ? candidateCount : config.MaxPowChecks; int powBudget = config.MaxPowChecks <= 0 ? candidateCount : config.MaxPowChecks;
int powUsed = 0; int powUsed = 0;
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates); Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
ExponentRemainderStepperCpu powStepper = ThreadStaticPools.RentExponentStepperCpu(divisorData); ExponentRemainderStepperCpu powStepper = orderFactors.ExponentRemainderStepper;
bool powStepperInitialized = false; powStepper.Reset();
int index = 0; ulong candidate = candidateSpan[0];
Span<ulong> stackGpuRemainders = stackalloc ulong[PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount];
ArrayPool<ulong> pool = ThreadStaticPools.UlongPool;
while (index < candidateCount && powUsed < powBudget)
{
int remaining = candidateCount - index;
int budgetRemaining = powBudget - powUsed;
int batchSize = Math.Min(remaining, Math.Min(budgetRemaining, PrimeOrderConstants.MaxGpuBatchSize));
if (batchSize <= 0)
{
break;
}
ReadOnlySpan<ulong> batch = candidateSpan.Slice(index, batchSize);
int startIndex = 0;
if (!powStepperInitialized)
{
ulong candidate = batch[0];
powUsed++; powUsed++;
bool equalsOne = powStepper.InitializeCpuIsUnity(candidate); bool equalsOne = powStepper.InitializeCpuIsUnity(candidate);
powStepperInitialized = true;
if (equalsOne && TryConfirmCandidateHybrid(gpu, prime, candidate, divisorData, config, ref powUsed, powBudget)) if (equalsOne && TryConfirmCandidateHybrid(gpu, prime, candidate, divisorData, config, ref powUsed, powBudget))
{ {
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
result = candidate; result = candidate;
return true; return true;
} }
startIndex = 1; int index = 1;
while (index < candidateCount && powUsed < powBudget)
{
int budgetRemaining = powBudget - powUsed;
if (budgetRemaining <= 0)
{
break;
} }
for (int i = startIndex; i < batchSize && powUsed < powBudget; i++) candidate = candidateSpan[index++];
{
ulong candidate = batch[i];
powUsed++; powUsed++;
bool equalsOne = powStepper.ComputeNextIsUnity(candidate); equalsOne = powStepper.ComputeNextIsUnity(candidate);
if (!equalsOne) if (!equalsOne)
{ {
continue; continue;
@@ -643,20 +559,10 @@ internal static partial class PrimeOrderCalculator
continue; continue;
} }
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
result = candidate; result = candidate;
return true; return true;
} }
index += batchSize;
}
candidates.Clear();
ThreadStaticPools.ReturnUlongList(candidates);
ThreadStaticPools.ReturnExponentStepperCpu(powStepper);
return false; return false;
} }
finally finally
@@ -666,7 +572,7 @@ internal static partial class PrimeOrderCalculator
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static bool TrySpecialMaxHybrid(PrimeOrderCalculatorAccelerator gpu, ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) private static bool TrySpecialMaxHybrid(PrimeOrderCalculatorAccelerator gpu, ulong phi, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
{ {
int length = factors.Count; int length = factors.Count;
// The phi factorization on the scanner path always yields at least one entry for phi >= 2, // The phi factorization on the scanner path always yields at least one entry for phi >= 2,
@@ -676,15 +582,13 @@ internal static partial class PrimeOrderCalculator
// return true; // return true;
// } // }
ReadOnlySpan<ulong> factorSpan = new(factors.Factors, 0, length);
Span<ulong> stackBuffer = stackalloc ulong[length];
if (length <= 8) if (length <= 8)
{ {
return EvaluateSpecialMaxCandidatesCpu(stackBuffer, factorSpan, phi, prime, divisorData); return EvaluateSpecialMaxCandidatesCpu(factors, phi);
} }
return EvaluateSpecialMaxCandidatesGpu(gpu, factorSpan, phi, prime, divisorData); ReadOnlySpan<ulong> factorSpan = new(factors.Factors, 0, length);
return EvaluateSpecialMaxCandidatesGpu(gpu, factorSpan, phi, divisorData);
} }
} }
+184 -125
View File
@@ -1,63 +1,89 @@
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator internal static partial class PrimeOrderCalculator
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void SortCandidatesWide(UInt128 prime, in UInt128? previousOrder, List<UInt128> candidates) private static void SortCandidatesWide(UInt128 prime, in UInt128? previousOrder, List<UInt128> candidates)
{ {
UInt128 previous = previousOrder ?? UInt128.Zero; UInt128 previous = previousOrder ?? UInt128.Zero;
int previousGroup = previousOrder.HasValue ? GetGroupWide(previousOrder.Value, prime) : 1;
bool hasPrevious = previousOrder.HasValue; bool hasPrevious = previousOrder.HasValue;
int previousGroup = hasPrevious ? GetGroupWide(previous, prime) : 1;
candidates.Sort((x, y) => candidates.Sort((left, right) => CompareCandidatesWide(prime, left, right, previous, hasPrevious, previousGroup));
{
CandidateKey128 keyX = BuildKeyWide(x, prime, previous, previousGroup, hasPrevious);
CandidateKey128 keyY = BuildKeyWide(y, prime, previous, previousGroup, hasPrevious);
int primary = keyX.Primary.CompareTo(keyY.Primary);
if (primary != 0)
{
return primary;
} }
int secondary = CompareComponents(keyX.SecondaryDescending, keyX.Secondary, keyY.SecondaryDescending, keyY.Secondary); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
if (secondary != 0) private static int CompareCandidatesWide(UInt128 prime, UInt128 left, UInt128 right, UInt128 previous, bool hasPrevious, int previousGroup)
{ {
return secondary; int leftGroup = GetGroupWide(left, prime);
} int rightGroup = GetGroupWide(right, prime);
if (leftGroup == 0)
return CompareComponents(keyX.TertiaryDescending, keyX.Tertiary, keyY.TertiaryDescending, keyY.Tertiary);
});
}
private static CandidateKey128 BuildKeyWide(
in UInt128 value,
in UInt128 prime,
in UInt128 previous,
int previousGroup,
bool hasPrevious)
{ {
int group = GetGroupWide(value, prime); return rightGroup == 0 ? 0 : 1;
if (group == 0) }
if (rightGroup == 0)
{ {
return new CandidateKey128(int.MaxValue, false, UInt128.Zero, false, UInt128.Zero); return -1;
} }
bool isGe = !hasPrevious || value >= previous; bool leftIsGe = !hasPrevious || left >= previous;
int primary = ComputePrimary(group, isGe, previousGroup); bool rightIsGe = !hasPrevious || right >= previous;
int leftPrimary = ComputePrimary(leftGroup, leftIsGe, previousGroup);
int rightPrimary = ComputePrimary(rightGroup, rightIsGe, previousGroup);
if (group == 3) int compare = leftPrimary.CompareTo(rightPrimary);
if (compare != 0)
{ {
return new CandidateKey128(primary, true, value, true, value); return compare;
} }
UInt128 distance = hasPrevious ? (value > previous ? value - previous : previous - value) : value; bool leftDescending = leftGroup == 3;
return new CandidateKey128(primary, false, distance, false, value); bool rightDescending = rightGroup == 3;
UInt128 leftSecondary;
UInt128 leftTertiary;
if (leftDescending)
{
leftSecondary = left;
leftTertiary = left;
}
else
{
UInt128 leftReference = hasPrevious ? (left > previous ? left - previous : previous - left) : left;
leftSecondary = leftReference;
leftTertiary = left;
} }
UInt128 rightSecondary;
UInt128 rightTertiary;
if (rightDescending)
{
rightSecondary = right;
rightTertiary = right;
}
else
{
UInt128 rightReference = hasPrevious ? (right > previous ? right - previous : previous - right) : right;
rightSecondary = rightReference;
rightTertiary = right;
}
compare = CompareComponents(leftDescending, leftSecondary, rightDescending, rightSecondary);
if (compare != 0)
{
return compare;
}
return CompareComponents(leftDescending, leftTertiary, rightDescending, rightTertiary);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static int CompareComponents(bool descendingX, in UInt128 valueX, bool descendingY, in UInt128 valueY) private static int CompareComponents(bool descendingX, in UInt128 valueX, bool descendingY, in UInt128 valueY)
{ {
if (descendingX == descendingY) if (descendingX == descendingY)
@@ -83,22 +109,23 @@ internal static partial class PrimeOrderCalculator
return valueX > valueY ? 1 : -1; return valueX > valueY ? 1 : -1;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static int GetGroupWide(in UInt128 value, in UInt128 prime) private static int GetGroupWide(in UInt128 value, in UInt128 prime)
{ {
UInt128 threshold1 = prime >> 3; UInt128 threshold = prime >> 3;
if (value <= threshold1) if (value <= threshold)
{ {
return 1; return 1;
} }
UInt128 threshold2 = prime >> 2; threshold = prime >> 2;
if (value <= threshold2) if (value <= threshold)
{ {
return 2; return 2;
} }
UInt128 threshold3 = (UInt128)(((BigInteger)prime * 3) >> 3); threshold = (UInt128)(((BigInteger)prime * 3) >> 3);
if (value <= threshold3) if (value <= threshold)
{ {
return 3; return 3;
} }
@@ -106,8 +133,10 @@ internal static partial class PrimeOrderCalculator
return 0; return 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void BuildCandidatesWide(in UInt128 order, FactorEntry128[] factors, int count, List<UInt128> candidates, int limit) private static void BuildCandidatesWide(in UInt128 order, FactorEntry128[] factors, int count, List<UInt128> candidates, int limit)
{ {
// TODO: Is this condition ever met on EvenPerfectBitScanner execution paths for this method? Can we remove it?
if (count == 0) if (count == 0)
{ {
return; return;
@@ -118,6 +147,7 @@ internal static partial class PrimeOrderCalculator
BuildCandidatesRecursiveWide(order, buffer, 0, UInt128.One, candidates, limit); BuildCandidatesRecursiveWide(order, buffer, 0, UInt128.One, candidates, limit);
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static void BuildCandidatesRecursiveWide( private static void BuildCandidatesRecursiveWide(
in UInt128 order, in UInt128 order,
in ReadOnlySpan<FactorEntry128> factors, in ReadOnlySpan<FactorEntry128> factors,
@@ -185,33 +215,14 @@ internal static partial class PrimeOrderCalculator
return PartialFactorResult128.Empty; return PartialFactorResult128.Empty;
} }
Dictionary<UInt128, int> counts = new(capacity: 8); PartialFactorResult128 result = PartialFactorResult128.Rent();
Dictionary<UInt128, int> counts = result.FactorCounts;
counts.Clear();
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit; uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
UInt128 remaining; UInt128 remaining = PopulateSmallPrimeFactorsCpuWide(value, limit, counts);
// if (IsGpuPow2Allowed && value <= ulong.MaxValue) List<UInt128> pending = result.Pending;
// { pending.Clear();
// Dictionary<ulong, int> narrowCounts = new(capacity: 8);
// if (TryPopulateSmallPrimeFactorsGpu((ulong)value, limit, narrowCounts, out ulong narrowRemaining))
// {
// foreach (KeyValuePair<ulong, int> entry in narrowCounts)
// {
// counts[(UInt128)entry.Key] = entry.Value;
// }
// remaining = (UInt128)narrowRemaining;
// }
// else
// {
// remaining = PopulateSmallPrimeFactorsCpuWide(value, limit, counts);
// }
// }
// else
// {
remaining = PopulateSmallPrimeFactorsCpuWide(value, limit, counts);
// }
List<UInt128> pending = new();
if (remaining > UInt128.One) if (remaining > UInt128.One)
{ {
pending.Add(remaining); pending.Add(remaining);
@@ -220,11 +231,12 @@ internal static partial class PrimeOrderCalculator
if (config.PollardRhoMilliseconds > 0 && pending.Count > 0) if (config.PollardRhoMilliseconds > 0 && pending.Count > 0)
{ {
long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds); long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds);
Stack<UInt128> stack = new(); FixedCapacityStack<UInt128> stack = result.CompositeStack;
stack.Clear();
stack.Push(remaining); stack.Push(remaining);
pending.Clear(); pending.Clear();
long timestamp = 0L; // reused for deadline checks. long timestamp; // reused for deadline checks.
while (stack.Count > 0) while (stack.Count > 0)
{ {
UInt128 composite = stack.Pop(); UInt128 composite = stack.Pop();
@@ -276,33 +288,13 @@ internal static partial class PrimeOrderCalculator
if (counts.Count == 0 && cofactor == value) if (counts.Count == 0 && cofactor == value)
{ {
return new PartialFactorResult128(null, value, false, 0); result.Cofactor = value;
// Everything else stays at default values initiated in .Rent.
return result;
} }
ArrayPool<FactorEntry128>? pool = null; result.InitializeFromCounts(counts, cofactor, cofactor == UInt128.One);
FactorEntry128[]? rented = counts.Count > 0 ? (pool = ThreadStaticPools.FactorEntry128Pool).Rent(counts.Count) : null; return result;
int index = 0;
if (rented is not null)
{
foreach (KeyValuePair<UInt128, int> entry in counts)
{
rented[index] = new FactorEntry128(entry.Key, entry.Value);
index++;
}
}
FactorEntry128[]? resultArray = null;
if (pool is not null)
{
Span<FactorEntry128> span = rented.AsSpan(0, index);
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
resultArray = new FactorEntry128[index];
span.CopyTo(resultArray);
pool.Return(rented!, clearArray: false);
}
bool fullyFactored = cofactor == UInt128.One;
return new PartialFactorResult128(resultArray, cofactor, fullyFactored, index);
} }
private static UInt128 PopulateSmallPrimeFactorsCpuWide(in UInt128 value, uint limit, Dictionary<UInt128, int> counts) private static UInt128 PopulateSmallPrimeFactorsCpuWide(in UInt128 value, uint limit, Dictionary<UInt128, int> counts)
@@ -357,7 +349,7 @@ internal static partial class PrimeOrderCalculator
UInt128 c = UInt128.One; UInt128 c = UInt128.One;
UInt128 x = (UInt128)2UL; UInt128 x = (UInt128)2UL;
UInt128 y = x; UInt128 y = x;
long timestamp = 0L; // reused for deadline checks. long timestamp; // reused for deadline checks.
while (true) while (true)
{ {
@@ -368,8 +360,7 @@ internal static partial class PrimeOrderCalculator
} }
x = AdvancePolynomialWide(x, c, n); x = AdvancePolynomialWide(x, c, n);
y = AdvancePolynomialWide(y, c, n); y = AdvancePolynomialTwiceWide(y, c, n);
y = AdvancePolynomialWide(y, c, n);
UInt128 diff = x > y ? x - y : y - x; UInt128 diff = x > y ? x - y : y - x;
UInt128 d = BinaryGcdWide(diff, n); UInt128 d = BinaryGcdWide(diff, n);
@@ -396,6 +387,14 @@ internal static partial class PrimeOrderCalculator
return (UInt128)value; return (UInt128)value;
} }
private static UInt128 AdvancePolynomialTwiceWide(in UInt128 x, in UInt128 c, in UInt128 modulus)
{
BigInteger value = (BigInteger)x;
value = (value * value + (BigInteger)c) % (BigInteger)modulus;
value = (value * value + (BigInteger)c) % (BigInteger)modulus;
return (UInt128)value;
}
private static UInt128 BinaryGcdWide(UInt128 a, UInt128 b) private static UInt128 BinaryGcdWide(UInt128 a, UInt128 b)
{ {
if (a == UInt128.Zero) if (a == UInt128.Zero)
@@ -479,8 +478,7 @@ internal static partial class PrimeOrderCalculator
while (d == UInt128.One) while (d == UInt128.One)
{ {
x = AdvancePolynomialWide(x, c, n); x = AdvancePolynomialWide(x, c, n);
y = AdvancePolynomialWide(y, c, n); y = AdvancePolynomialTwiceWide(y, c, n);
y = AdvancePolynomialWide(y, c, n);
UInt128 diff = x > y ? x - y : y - x; UInt128 diff = x > y ? x - y : y - x;
d = BinaryGcdWide(diff, n); d = BinaryGcdWide(diff, n);
@@ -509,12 +507,13 @@ internal static partial class PrimeOrderCalculator
} }
uint[] smallPrimes = PrimesGenerator.SmallPrimes; uint[] smallPrimes = PrimesGenerator.SmallPrimes;
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
int length = smallPrimes.Length; int length = smallPrimes.Length;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
uint prime = smallPrimes[i]; uint prime = smallPrimes[i];
UInt128 primeValue = prime; UInt128 primeValue = prime;
UInt128 primeSquare = primeValue * primeValue; UInt128 primeSquare = (UInt128)smallPrimesPow2[i];
if (primeSquare > value) if (primeSquare > value)
{ {
break; break;
@@ -588,44 +587,104 @@ internal static partial class PrimeOrderCalculator
} }
} }
private readonly struct CandidateKey128(int primary, bool secondaryDescending, in UInt128 secondary, bool tertiaryDescending, in UInt128 tertiary) private sealed class PartialFactorResult128
{ {
public int Primary { get; } = primary; [ThreadStatic]
private static PartialFactorResult128? s_poolHead;
public bool SecondaryDescending { get; } = secondaryDescending; public static readonly PartialFactorResult128 Empty = new()
{
Cofactor = UInt128.One,
FullyFactored = true,
Count = 0
};
public UInt128 Secondary { get; } = secondary; private const int ExponentHardLimit = 256;
public bool TertiaryDescending { get; } = tertiaryDescending; internal readonly FactorEntry128[] TempFactors = new FactorEntry128[ExponentHardLimit];
internal readonly List<UInt128> FactorCandidatesList = new(ExponentHardLimit);
internal readonly Dictionary<UInt128, int> FactorCounts = new(capacity: ExponentHardLimit);
internal readonly List<UInt128> Pending = new(capacity: ExponentHardLimit);
internal readonly FixedCapacityStack<UInt128> CompositeStack = new(ExponentHardLimit);
public UInt128 Tertiary { get; } = tertiary; private PartialFactorResult128? _next;
public UInt128 Cofactor;
public int Count;
public readonly FactorEntry128[] Factors = new FactorEntry128[ExponentHardLimit];
public bool FullyFactored;
public bool HasFactors;
private PartialFactorResult128()
{
} }
private readonly struct PartialFactorResult128(FactorEntry128[]? factors, UInt128 cofactor, bool fullyFactored, int count) public static PartialFactorResult128 Rent()
{ {
public FactorEntry128[]? Factors { get; } = factors; if (s_poolHead is { } instance)
public UInt128 Cofactor { get; } = cofactor;
public bool FullyFactored { get; } = fullyFactored;
public int Count { get; } = count;
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
public PartialFactorResult128 WithAdditionalPrime(UInt128 prime)
{ {
if (Factors is null) s_poolHead = instance._next;
{ instance._next = null;
FactorEntry128[] local = new FactorEntry128[1];
local[0] = new FactorEntry128(prime, 1); instance.Cofactor = UInt128.One;
return new PartialFactorResult128(local, UInt128.One, true, 1); instance.Count = 0;
instance.FullyFactored = false;
instance.HasFactors = false;
return instance;
} }
FactorEntry128[] extended = new FactorEntry128[Count + 1]; return new PartialFactorResult128();
Array.Copy(Factors, extended, Count); }
extended[Count] = new FactorEntry128(prime, 1);
return new PartialFactorResult128(extended, UInt128.One, true, Count + 1); public void WithAdditionalPrime(UInt128 prime)
{
int index = Count;
FactorEntry128[] buffer = Factors;
buffer[index] = new FactorEntry128(prime, 1);
index++;
Count = index;
buffer.AsSpan(0, index).Sort(static (a, b) => a.Value.CompareTo(b.Value));
Cofactor = UInt128.One;
FullyFactored = true;
HasFactors = true;
}
public void Dispose()
{
if (this == Empty)
{
return;
}
_next = s_poolHead;
s_poolHead = this;
}
internal void InitializeFromCounts(Dictionary<UInt128, int> counts, UInt128 cofactor, bool fullyFactored)
{
int count = counts.Count;
Count = count;
Cofactor = cofactor;
FullyFactored = fullyFactored;
bool hasFactors = count != 0;
HasFactors = hasFactors;
if (!hasFactors)
{
return;
}
int index = 0;
foreach (KeyValuePair<UInt128, int> entry in counts)
{
Factors[index++] = new FactorEntry128(entry.Key, entry.Value);
}
// We benefit from static (non-virtual) call to Sort. When JIT finds a method call after a constructor, it optimizes it to a true static call.
new Span<FactorEntry128>(Factors, 0, count).Sort(static (a, b) => a.Value.CompareTo(b.Value));
} }
} }
} }
+5 -5
View File
@@ -263,12 +263,12 @@ namespace PerfectNumbers.Core
public static void Return((ulong, ulong)[] array) => _ulongUlongTupleArrayPool!.Return(array, clearArray: false); public static void Return((ulong, ulong)[] array) => _ulongUlongTupleArrayPool!.Return(array, clearArray: false);
[ThreadStatic] [ThreadStatic]
private static FixedCapacityStack<List<PrimeOrderCalculator.PendingEntry>>? _primeOrderPendingEntryListPool; private static FixedCapacityStack<List<PartialFactorPendingEntry>>? _primeOrderPendingEntryListPool;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static List<PrimeOrderCalculator.PendingEntry> RentPrimeOrderPendingEntryList(int capacityHint) internal static List<PartialFactorPendingEntry> RentPrimeOrderPendingEntryList(int capacityHint)
{ {
FixedCapacityStack<List<PrimeOrderCalculator.PendingEntry>>? pool = _primeOrderPendingEntryListPool; FixedCapacityStack<List<PartialFactorPendingEntry>>? pool = _primeOrderPendingEntryListPool;
if (pool is not null) if (pool is not null)
{ {
if (pool.Pop() is { } list) if (pool.Pop() is { } list)
@@ -286,11 +286,11 @@ namespace PerfectNumbers.Core
_primeOrderPendingEntryListPool = new(PerfectNumberConstants.DefaultPoolCapacity); _primeOrderPendingEntryListPool = new(PerfectNumberConstants.DefaultPoolCapacity);
} }
return new List<PrimeOrderCalculator.PendingEntry>(capacityHint); return new List<PartialFactorPendingEntry>(capacityHint);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static void ReturnPrimeOrderPendingEntryList(List<PrimeOrderCalculator.PendingEntry> list) => _primeOrderPendingEntryListPool!.Push(list); internal static void ReturnPrimeOrderPendingEntryList(List<PartialFactorPendingEntry> list) => _primeOrderPendingEntryListPool!.Push(list);
[ThreadStatic] [ThreadStatic]
private static FixedCapacityStack<FixedCapacityStack<ulong>>? _ulongStackPool; private static FixedCapacityStack<FixedCapacityStack<ulong>>? _ulongStackPool;