Reduce CPU partial factor allocations

This commit is contained in:
Marek Lemańczyk committed 2025-10-10 00:07:39 +02:00
1 parent 74e9e88977
commit 8054f9fe59
1 file changed
+442 -327
+442 -327
View File
@@ -95,6 +95,15 @@ internal static partial class PrimeOrderCalculator
[ThreadStatic] [ThreadStatic]
private static bool s_debugLoggingEnabled; private static bool s_debugLoggingEnabled;
[ThreadStatic]
private static List<ulong>? s_partialFactorPending;
[ThreadStatic]
private static Stack<ulong>? s_partialFactorCompositeStack;
[ThreadStatic]
private static Dictionary<ulong, int>? s_partialFactorCounts;
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu; private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
private readonly struct Pow2ModeScope : IDisposable private readonly struct Pow2ModeScope : IDisposable
{ {
@@ -189,7 +198,7 @@ internal static partial class PrimeOrderCalculator
using var debugScope = UseDebugLogging(!IsGpuHeuristicDevice); using var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
// DebugLog("Partial factoring φ(p)"); // DebugLog("Partial factoring φ(p)");
PartialFactorResult phiFactors = PartialFactor(phi, config); using PartialFactorResult phiFactors = PartialFactor(phi, config);
if (phiFactors.Factors is null) if (phiFactors.Factors is null)
{ {
@@ -353,54 +362,62 @@ internal static partial class PrimeOrderCalculator
// TODO: Do we do partial factoring of order multiple times? // TODO: Do we do partial factoring of order multiple times?
PartialFactorResult factorization = PartialFactor(order, config); PartialFactorResult factorization = PartialFactor(order, config);
if (factorization.Factors is null) try
{ {
return false; if (factorization.Factors is null)
}
if (!factorization.FullyFactored)
{
if (factorization.Cofactor <= 1UL)
{
// DebugLog("Cofactor <= 1. No factors found");
return false;
}
// DebugLog("Cofactor > 1. Testing primality of cofactor");
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factorization.Cofactor);
// bool isPrime = PrimeTester.IsPrimeInternal(factorization.Cofactor, CancellationToken.None);
if (!isPrime)
{ {
return false; return false;
} }
// DebugLog("Adding cofactor as prime factor"); if (!factorization.FullyFactored)
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
}
ReadOnlySpan<FactorEntry> span = factorization.Factors;
// DebugLog("Verifying prime-power reductions");
int length = factorization.Count;
for (int i = 0; i < length; i++)
{
ulong primeFactor = span[i].Value;
ulong reduced = order;
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{ {
if ((reduced % primeFactor) != 0UL) if (factorization.Cofactor <= 1UL)
{
break;
}
reduced /= primeFactor;
if (Pow2EqualsOne(reduced, prime, divisorData))
{ {
return false; return false;
} }
}
}
return true; // DebugLog("Cofactor > 1. Testing primality of cofactor");
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factorization.Cofactor);
// bool isPrime = PrimeTester.IsPrimeInternal(factorization.Cofactor, CancellationToken.None);
if (!isPrime)
{
return false;
}
// DebugLog("Adding cofactor as prime factor");
PartialFactorResult extended = factorization.WithAdditionalPrime(factorization.Cofactor);
factorization.Dispose();
factorization = extended;
}
ReadOnlySpan<FactorEntry> span = factorization.Factors;
// DebugLog("Verifying prime-power reductions");
int length = factorization.Count;
for (int i = 0; i < length; i++)
{
ulong primeFactor = span[i].Value;
ulong reduced = order;
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{
if ((reduced % primeFactor) != 0UL)
{
break;
}
reduced /= primeFactor;
if (Pow2EqualsOne(reduced, prime, divisorData))
{
return false;
}
}
}
return true;
}
finally
{
factorization.Dispose();
}
} }
private static bool TryHeuristicFinish( private static bool TryHeuristicFinish(
@@ -421,127 +438,140 @@ internal static partial class PrimeOrderCalculator
// TODO: Do we do partial factoring of order multiple times? // TODO: Do we do partial factoring of order multiple times?
// DebugLog("Trying heuristic. Partial factoring order"); // DebugLog("Trying heuristic. Partial factoring order");
PartialFactorResult orderFactors = PartialFactor(order, config); PartialFactorResult orderFactors = PartialFactor(order, config);
if (orderFactors.Factors is null) try
{ {
return false; if (orderFactors.Factors is null)
}
if (!orderFactors.FullyFactored)
{
if (orderFactors.Cofactor <= 1UL)
{ {
return false; return false;
} }
var isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(orderFactors.Cofactor); if (!orderFactors.FullyFactored)
// if (!PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None))
if (!isPrime)
{ {
return false; if (orderFactors.Cofactor <= 1UL)
}
orderFactors = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
}
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
List<ulong> candidates = new(capacity);
FactorEntry[] factorArray = orderFactors.Factors!;
// DebugLog("Building candidates list");
BuildCandidates(order, factorArray, orderFactors.Count, candidates, capacity);
if (candidates.Count == 0)
{
return false;
}
// DebugLog("Sorting candidates");
SortCandidates(prime, previousOrder, candidates);
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
int powUsed = 0;
int candidateCount = candidates.Count;
bool allowGpuBatch = true;
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
// DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)");
int index = 0;
const int MaxGpuBatchSize = 1024;
const int StackGpuBatchSize = 1024;
Span<ulong> stackGpuRemainders = stackalloc ulong[StackGpuBatchSize];
while (index < candidateCount && powUsed < powBudget)
{
int remaining = candidateCount - index;
int budgetRemaining = powBudget - powUsed;
int batchSize = Math.Min(remaining, Math.Min(budgetRemaining, MaxGpuBatchSize));
if (batchSize <= 0)
{
break;
}
ReadOnlySpan<ulong> batch = candidateSpan.Slice(index, batchSize);
ulong[]? gpuPool = null;
Span<ulong> pooledGpuRemainders = default;
bool gpuSuccess = false;
bool gpuStackRemainders = false;
GpuPow2ModStatus status = GpuPow2ModStatus.Unavailable;
if (allowGpuBatch && IsGpuPow2Allowed)
{
if (batchSize <= StackGpuBatchSize)
{ {
Span<ulong> localRemainders = stackGpuRemainders.Slice(0, batchSize); return false;
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, localRemainders, divisorData);
if (status == GpuPow2ModStatus.Success)
{
gpuSuccess = true;
gpuStackRemainders = true;
}
} }
else
var isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(orderFactors.Cofactor);
// if (!PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None))
if (!isPrime)
{ {
gpuPool = ArrayPool<ulong>.Shared.Rent(batchSize); return false;
Span<ulong> pooledRemainders = gpuPool.AsSpan(0, batchSize); }
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, pooledRemainders, divisorData);
if (status == GpuPow2ModStatus.Success) PartialFactorResult extended = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
orderFactors.Dispose();
orderFactors = extended;
}
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
List<ulong> candidates = new(capacity);
FactorEntry[] factorArray = orderFactors.Factors!;
// DebugLog("Building candidates list");
BuildCandidates(order, factorArray, orderFactors.Count, candidates, capacity);
if (candidates.Count == 0)
{
return false;
}
// DebugLog("Sorting candidates");
SortCandidates(prime, previousOrder, candidates);
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
int powUsed = 0;
int candidateCount = candidates.Count;
bool allowGpuBatch = true;
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
// DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)");
int index = 0;
const int MaxGpuBatchSize = 1024;
const int StackGpuBatchSize = 1024;
Span<ulong> stackGpuRemainders = stackalloc ulong[StackGpuBatchSize];
while (index < candidateCount && powUsed < powBudget)
{
int remaining = candidateCount - index;
int budgetRemaining = powBudget - powUsed;
int batchSize = Math.Min(remaining, Math.Min(budgetRemaining, MaxGpuBatchSize));
if (batchSize <= 0)
{
break;
}
ReadOnlySpan<ulong> batch = candidateSpan.Slice(index, batchSize);
ulong[]? gpuPool = null;
Span<ulong> pooledGpuRemainders = default;
bool gpuSuccess = false;
bool gpuStackRemainders = false;
GpuPow2ModStatus status = GpuPow2ModStatus.Unavailable;
if (allowGpuBatch && IsGpuPow2Allowed)
{
if (batchSize <= StackGpuBatchSize)
{ {
pooledGpuRemainders = pooledRemainders; Span<ulong> localRemainders = stackGpuRemainders.Slice(0, batchSize);
gpuSuccess = true; status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, localRemainders, divisorData);
if (status == GpuPow2ModStatus.Success)
{
gpuSuccess = true;
gpuStackRemainders = true;
}
} }
else else
{ {
ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false); gpuPool = ArrayPool<ulong>.Shared.Rent(batchSize);
gpuPool = null; Span<ulong> pooledRemainders = gpuPool.AsSpan(0, batchSize);
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, pooledRemainders, divisorData);
if (status == GpuPow2ModStatus.Success)
{
pooledGpuRemainders = pooledRemainders;
gpuSuccess = true;
}
else
{
ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false);
gpuPool = null;
}
}
if (!gpuSuccess && (status == GpuPow2ModStatus.Overflow || status == GpuPow2ModStatus.Unavailable))
{
allowGpuBatch = false;
} }
} }
if (!gpuSuccess && (status == GpuPow2ModStatus.Overflow || status == GpuPow2ModStatus.Unavailable)) for (int i = 0; i < batchSize && powUsed < powBudget; i++)
{ {
allowGpuBatch = false; ulong candidate = batch[i];
} powUsed++;
}
for (int i = 0; i < batchSize && powUsed < powBudget; i++) bool equalsOne;
{ if (gpuSuccess)
ulong candidate = batch[i]; {
powUsed++; ulong remainderValue = gpuStackRemainders ? stackGpuRemainders[i] : pooledGpuRemainders[i];
equalsOne = remainderValue == 1UL;
}
else
{
equalsOne = Pow2EqualsOne(candidate, prime, divisorData);
}
if (!equalsOne)
{
continue;
}
bool equalsOne; if (!TryConfirmCandidate(prime, candidate, divisorData, config, ref powUsed, powBudget))
if (gpuSuccess) {
{ continue;
ulong remainderValue = gpuStackRemainders ? stackGpuRemainders[i] : pooledGpuRemainders[i]; }
equalsOne = remainderValue == 1UL;
}
else
{
equalsOne = Pow2EqualsOne(candidate, prime, divisorData);
}
if (!equalsOne)
{
continue;
}
if (!TryConfirmCandidate(prime, candidate, divisorData, config, ref powUsed, powBudget)) if (gpuPool is not null)
{ {
continue; ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false);
}
result = candidate;
return true;
} }
if (gpuPool is not null) if (gpuPool is not null)
@@ -549,20 +579,16 @@ internal static partial class PrimeOrderCalculator
ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false); ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false);
} }
result = candidate; index += batchSize;
return true;
} }
if (gpuPool is not null) // DebugLog("No candidate confirmed");
{ return false;
ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false); }
} finally
{
index += batchSize; orderFactors.Dispose();
} }
// DebugLog("No candidate confirmed");
return false;
} }
private static void SortCandidates(ulong prime, ulong? previousOrder, List<ulong> candidates) private static void SortCandidates(ulong prime, ulong? previousOrder, List<ulong> candidates)
@@ -735,56 +761,65 @@ internal static partial class PrimeOrderCalculator
private static bool TryConfirmCandidate(ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config, ref int powUsed, int powBudget) private static bool TryConfirmCandidate(ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config, ref int powUsed, int powBudget)
{ {
PartialFactorResult factorization = PartialFactor(candidate, config); PartialFactorResult factorization = PartialFactor(candidate, config);
if (factorization.Factors is null) try
{ {
return false; if (factorization.Factors is null)
}
if (!factorization.FullyFactored)
{
if (factorization.Cofactor <= 1UL)
{ {
return false; return false;
} }
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factorization.Cofactor); if (!factorization.FullyFactored)
// bool isPrime = PrimeTester.IsPrimeInternal(factorization.Cofactor, CancellationToken.None);
if (!isPrime)
{ {
return false; if (factorization.Cofactor <= 1UL)
{
return false;
}
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factorization.Cofactor);
// bool isPrime = PrimeTester.IsPrimeInternal(factorization.Cofactor, CancellationToken.None);
if (!isPrime)
{
return false;
}
PartialFactorResult extended = factorization.WithAdditionalPrime(factorization.Cofactor);
factorization.Dispose();
factorization = extended;
} }
factorization = factorization.WithAdditionalPrime(factorization.Cofactor); ReadOnlySpan<FactorEntry> span = factorization.Factors;
} int length = factorization.Count;
for (int i = 0; i < length; i++)
{
ulong primeFactor = span[i].Value;
ulong reduced = candidate;
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{
if ((reduced % primeFactor) != 0UL)
{
break;
}
ReadOnlySpan<FactorEntry> span = factorization.Factors; reduced /= primeFactor;
int length = factorization.Count; if (powUsed >= powBudget && powBudget > 0)
for (int i = 0; i < length; i++) {
return false;
}
powUsed++;
if (Pow2EqualsOne(reduced, prime, divisorData))
{
return false;
}
}
}
return true;
}
finally
{ {
ulong primeFactor = span[i].Value; factorization.Dispose();
ulong reduced = candidate;
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{
if ((reduced % primeFactor) != 0UL)
{
break;
}
reduced /= primeFactor;
if (powUsed >= powBudget && powBudget > 0)
{
return false;
}
powUsed++;
if (Pow2EqualsOne(reduced, prime, divisorData))
{
return false;
}
}
} }
return true;
} }
private static bool Pow2EqualsOne(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData) private static bool Pow2EqualsOne(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData)
@@ -905,6 +940,56 @@ internal static partial class PrimeOrderCalculator
} }
} }
private static List<ulong> AcquirePendingCompositeList(int capacityHint)
{
List<ulong>? list = s_partialFactorPending;
if (list is null)
{
list = new List<ulong>(capacityHint);
s_partialFactorPending = list;
return list;
}
list.Clear();
if (list.Capacity < capacityHint)
{
list.Capacity = capacityHint;
}
return list;
}
private static Stack<ulong> AcquireCompositeStack(int capacityHint)
{
Stack<ulong>? stack = s_partialFactorCompositeStack;
if (stack is null)
{
stack = new Stack<ulong>(capacityHint);
s_partialFactorCompositeStack = stack;
return stack;
}
stack.Clear();
stack.EnsureCapacity(capacityHint);
return stack;
}
private static Dictionary<ulong, int> AcquireFactorCountDictionary(int capacityHint)
{
Dictionary<ulong, int>? dictionary = s_partialFactorCounts;
int adjustedCapacity = Math.Max(capacityHint, 8);
if (dictionary is null)
{
dictionary = new Dictionary<ulong, int>(adjustedCapacity);
s_partialFactorCounts = dictionary;
return dictionary;
}
dictionary.Clear();
dictionary.EnsureCapacity(adjustedCapacity);
return dictionary;
}
private static PartialFactorResult PartialFactor(ulong value, PrimeOrderSearchConfig config) private static PartialFactorResult PartialFactor(ulong value, PrimeOrderSearchConfig config)
{ {
if (value <= 1UL) if (value <= 1UL)
@@ -923,166 +1008,165 @@ internal static partial class PrimeOrderCalculator
bool useDictionary = false; bool useDictionary = false;
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit; uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
ulong remaining; ulong remaining = value;
bool gpuFactored = false; bool gpuFactored = false;
if (IsGpuHeuristicDevice) if (IsGpuHeuristicDevice)
{ {
gpuFactored = PrimeOrderGpuHeuristics.TryPartialFactor(value, limit, primeSlots, exponentSlots, out factorCount, out remaining, out _); gpuFactored = PrimeOrderGpuHeuristics.TryPartialFactor(value, limit, primeSlots, exponentSlots, out factorCount, out remaining, out _);
} }
else
{
remaining = value;
}
if (!gpuFactored) List<ulong> pending = AcquirePendingCompositeList(2);
Stack<ulong>? compositeStack = null;
try
{ {
primeSlots.Clear(); if (!gpuFactored)
exponentSlots.Clear();
if (!TryPopulateSmallPrimeFactorsCpu(value, limit, primeSlots, exponentSlots, out factorCount, out remaining))
{ {
useDictionary = true; primeSlots.Clear();
counts = new Dictionary<ulong, int>(capacity: 8); exponentSlots.Clear();
remaining = PopulateSmallPrimeFactorsCpu(value, limit, counts); if (!TryPopulateSmallPrimeFactorsCpu(value, limit, primeSlots, exponentSlots, out factorCount, out remaining))
}
}
List<ulong> pending = new();
if (remaining > 1UL)
{
pending.Add(remaining);
}
if (config.PollardRhoMilliseconds > 0 && pending.Count > 0)
{
// DebugLog("Processing pending composites with Pollard's Rho");
Stopwatch stopwatch = Stopwatch.StartNew();
long budgetTicks = TimeSpan.FromMilliseconds(config.PollardRhoMilliseconds).Ticks;
Stack<ulong> stack = new();
stack.Push(remaining);
pending.Clear();
while (stack.Count > 0)
{
ulong composite = stack.Pop();
if (composite <= 1UL)
{ {
continue; useDictionary = true;
counts = AcquireFactorCountDictionary(Math.Max(factorCount, 8));
remaining = PopulateSmallPrimeFactorsCpu(value, limit, counts);
} }
}
if (remaining > 1UL)
{
pending.Add(remaining);
}
if (config.PollardRhoMilliseconds > 0 && pending.Count > 0)
{
Stopwatch stopwatch = Stopwatch.StartNew();
long budgetTicks = TimeSpan.FromMilliseconds(config.PollardRhoMilliseconds).Ticks;
compositeStack = AcquireCompositeStack(Math.Max(pending.Count * 2, 4));
compositeStack.Push(remaining);
pending.Clear();
while (compositeStack.Count > 0)
{
ulong composite = compositeStack.Pop();
if (composite <= 1UL)
{
continue;
}
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite);
if (isPrime)
{
AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1);
continue;
}
if (stopwatch.ElapsedTicks > budgetTicks)
{
pending.Add(composite);
continue;
}
if (!TryPollardRho(composite, stopwatch, budgetTicks, out ulong factor))
{
pending.Add(composite);
continue;
}
ulong quotient = composite / factor;
compositeStack.Push(factor);
compositeStack.Push(quotient);
}
}
ulong cofactor = 1UL;
int pendingCount = pending.Count;
for (int i = 0; i < pendingCount; i++)
{
ulong composite = pending[i];
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite);
if (isPrime) if (isPrime)
{ {
AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1);
continue;
} }
else if (composite > 1UL)
if (stopwatch.ElapsedTicks > budgetTicks)
{ {
pending.Add(composite); cofactor = checked(cofactor * composite);
continue;
} }
}
if (!TryPollardRho(composite, stopwatch, budgetTicks, out ulong factor)) if (useDictionary)
{
if ((counts is null || counts.Count == 0) && cofactor == value)
{ {
pending.Add(composite); return new PartialFactorResult(null, value, false, 0);
continue;
} }
ulong quotient = composite / factor; if (counts is null || counts.Count == 0)
stack.Push(factor); {
stack.Push(quotient); return new PartialFactorResult(null, cofactor, cofactor == 1UL, 0);
} }
}
ulong cofactor = 1UL; FactorEntry[] factors = ArrayPool<FactorEntry>.Shared.Rent(counts.Count);
int pendingCount = pending.Count; int index = 0;
for (int i = 0; i < pendingCount; i++) foreach (KeyValuePair<ulong, int> entry in counts)
{ {
ulong composite = pending[i]; factors[index] = new FactorEntry(entry.Key, entry.Value);
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); index++;
if (isPrime) }
{
AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1);
}
else if (composite > 1UL)
{
cofactor = checked(cofactor * composite);
}
}
if (useDictionary) Span<FactorEntry> span = factors.AsSpan(0, index);
{ span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
if ((counts is null || counts.Count == 0) && cofactor == value)
{ bool fullyFactored = cofactor == 1UL;
// DebugLog("cofactor is the same as value, no factors found"); return new PartialFactorResult(factors, cofactor, fullyFactored, index, pooled: true);
return new PartialFactorResult(null, value, false, 0);
} }
if (counts is null || counts.Count == 0) int actualCount = 0;
for (int i = 0; i < factorCount; i++)
{ {
if (primeSlots[i] != 0UL && exponentSlots[i] != 0)
{
actualCount++;
}
}
if (actualCount == 0)
{
if (cofactor == value)
{
return new PartialFactorResult(null, value, false, 0);
}
return new PartialFactorResult(null, cofactor, cofactor == 1UL, 0); return new PartialFactorResult(null, cofactor, cofactor == 1UL, 0);
} }
FactorEntry[] factors = ArrayPool<FactorEntry>.Shared.Rent(counts.Count); FactorEntry[] array = ArrayPool<FactorEntry>.Shared.Rent(actualCount);
int index = 0; int arrayIndex = 0;
// DebugLog(() => $"Collecting {counts.Count} prime factors"); for (int i = 0; i < factorCount; i++)
foreach (KeyValuePair<ulong, int> entry in counts)
{ {
factors[index] = new FactorEntry(entry.Key, entry.Value); ulong primeValue = primeSlots[i];
index++; int exponentValue = exponentSlots[i];
if (primeValue == 0UL || exponentValue == 0)
{
continue;
}
array[arrayIndex] = new FactorEntry(primeValue, exponentValue);
arrayIndex++;
} }
Span<FactorEntry> span = factors.AsSpan(0, index); Span<FactorEntry> arraySpan = array.AsSpan(0, arrayIndex);
span.Sort(static (a, b) => a.Value.CompareTo(b.Value)); arraySpan.Sort(static (a, b) => a.Value.CompareTo(b.Value));
bool fullyFactoredArray = cofactor == 1UL;
FactorEntry[] resultArray = new FactorEntry[index]; return new PartialFactorResult(array, cofactor, fullyFactoredArray, arrayIndex, pooled: true);
span.CopyTo(resultArray);
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
bool fullyFactored = cofactor == 1UL;
return new PartialFactorResult(resultArray, cofactor, fullyFactored, index);
} }
finally
int actualCount = 0;
for (int i = 0; i < factorCount; i++)
{ {
if (primeSlots[i] != 0UL && exponentSlots[i] != 0) pending.Clear();
{ counts?.Clear();
actualCount++; compositeStack?.Clear();
}
} }
if (actualCount == 0)
{
if (cofactor == value)
{
// DebugLog("cofactor is the same as value, no factors found");
return new PartialFactorResult(null, value, false, 0);
}
return new PartialFactorResult(null, cofactor, cofactor == 1UL, 0);
}
FactorEntry[] array = new FactorEntry[actualCount];
int arrayIndex = 0;
// DebugLog(() => $"Collecting {actualCount} prime factors");
for (int i = 0; i < factorCount; i++)
{
ulong primeValue = primeSlots[i];
int exponentValue = exponentSlots[i];
if (primeValue == 0UL || exponentValue == 0)
{
continue;
}
array[arrayIndex] = new FactorEntry(primeValue, exponentValue);
arrayIndex++;
}
Array.Sort(array, static (a, b) => a.Value.CompareTo(b.Value));
bool fullyFactoredArray = cofactor == 1UL;
return new PartialFactorResult(array, cofactor, fullyFactoredArray, array.Length);
} }
private const int GpuSmallPrimeFactorSlots = 64; private const int GpuSmallPrimeFactorSlots = 64;
@@ -1374,7 +1458,7 @@ internal static partial class PrimeOrderCalculator
return; return;
} }
counts ??= new Dictionary<ulong, int>(Math.Max(count, 8)); counts ??= AcquireFactorCountDictionary(Math.Max(count, 8));
CopyFactorsToDictionary(primes, exponents, count, counts); CopyFactorsToDictionary(primes, exponents, count, counts);
count = 0; count = 0;
useDictionary = true; useDictionary = true;
@@ -1505,27 +1589,58 @@ internal static partial class PrimeOrderCalculator
public readonly int Exponent = exponent; public readonly int Exponent = exponent;
} }
private readonly struct PartialFactorResult(FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count) private struct PartialFactorResult : IDisposable
{ {
public readonly FactorEntry[]? Factors = factors; private FactorEntry[]? _factors;
public readonly ulong Cofactor = cofactor; private readonly bool _pooled;
public readonly bool FullyFactored = fullyFactored;
public readonly int Count = count; public PartialFactorResult(FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count, bool pooled = false)
public readonly static PartialFactorResult Empty = new(null, 1UL, true, 0); {
_factors = factors;
Cofactor = cofactor;
FullyFactored = fullyFactored;
Count = count;
_pooled = pooled && factors is not null;
}
public FactorEntry[]? Factors => _factors;
public ulong Cofactor { get; }
public bool FullyFactored { get; }
public int Count { get; }
public static PartialFactorResult Empty => new(null, 1UL, true, 0);
public void Dispose()
{
FactorEntry[]? factors = _factors;
if (factors is null || !_pooled)
{
return;
}
_factors = null;
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
}
public PartialFactorResult WithAdditionalPrime(ulong prime) public PartialFactorResult WithAdditionalPrime(ulong prime)
{ {
if (Factors is null) if (_factors is null || Count == 0)
{ {
FactorEntry[] local = [new FactorEntry(prime, 1)]; FactorEntry[] local = ArrayPool<FactorEntry>.Shared.Rent(1);
return new PartialFactorResult(local, 1UL, true, 1); local[0] = new FactorEntry(prime, 1);
return new PartialFactorResult(local, 1UL, true, 1, pooled: true);
} }
FactorEntry[] extended = new FactorEntry[Count + 1]; FactorEntry[] extended = ArrayPool<FactorEntry>.Shared.Rent(Count + 1);
Array.Copy(Factors, 0, extended, 0, Count); Array.Copy(_factors, 0, extended, 0, Count);
extended[Count] = new FactorEntry(prime, 1); extended[Count] = new FactorEntry(prime, 1);
Array.Sort(extended, static (a, b) => a.Value.CompareTo(b.Value)); Span<FactorEntry> span = extended.AsSpan(0, Count + 1);
return new PartialFactorResult(extended, 1UL, true, Count + 1); span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
return new PartialFactorResult(extended, 1UL, true, Count + 1, pooled: true);
} }
} }
} }