Merge pull request #148 from mlemanczyk/codex/analyze-and-comment-mersennedivisorcycles-method-calls
Restore GPU heuristics and reuse cached order factorization
This commit is contained in:
4 files changed
+258
-231
No files matched your search
@@ -7,6 +7,9 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
|
|
||||||
public class PrimeOrderCalculatorTests
|
public class PrimeOrderCalculatorTests
|
||||||
{
|
{
|
||||||
|
// EvenPerfectBitScanner never exercises the GPU heuristic device or the small-prime guards that the legacy tests cover,
|
||||||
|
// so those scenarios are commented out because they are unreachable in the production pipeline.
|
||||||
|
/*
|
||||||
public static IEnumerable<object[]> SmallPrimes()
|
public static IEnumerable<object[]> SmallPrimes()
|
||||||
{
|
{
|
||||||
yield return new object[] { 3UL };
|
yield return new object[] { 3UL };
|
||||||
@@ -197,4 +200,5 @@ public class PrimeOrderCalculatorTests
|
|||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
@@ -232,6 +232,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
||||||
|
// EvenPerfectBitScanner reaches this path only for divisors above the precomputed cycle snapshot.
|
||||||
|
// Those candidates follow q = 2 * prime * k + 1 with prime >= 5, so the downstream order calculator
|
||||||
|
// can rely on an odd divisor and a prime exponent when deriving the cycle length.
|
||||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||||
|
|||||||
@@ -273,17 +273,24 @@ public class MersenneDivisorCycles
|
|||||||
{
|
{
|
||||||
cycleLength = 0UL;
|
cycleLength = 0UL;
|
||||||
|
|
||||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
// The by-divisor scanner only supplies odd candidates of the form 2*k*exponent + 1,
|
||||||
{
|
// so this power-of-two fast-path never triggers on that call path.
|
||||||
cycleLength = 1UL;
|
// The historical fast-path is left here for reference but commented out because it is unreachable
|
||||||
return true;
|
// on the EvenPerfectBitScanner pipeline.
|
||||||
}
|
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||||
|
// {
|
||||||
|
// cycleLength = 1UL;
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
if (divisor <= 3UL || exponent <= 1UL)
|
// EvenPerfectBitScanner filters exponents <= 3 before invoking this helper and each
|
||||||
{
|
// divisor candidate is at least 2 * exponent + 1, so this guard never matches.
|
||||||
cycleLength = CalculateCycleLength(divisor, divisorData);
|
// The fallback calculation is therefore unreachable on the active call path.
|
||||||
return true;
|
// if (divisor <= 3UL || exponent <= 1UL)
|
||||||
}
|
// {
|
||||||
|
// cycleLength = CalculateCycleLength(divisor, divisorData);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
@@ -298,29 +305,38 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong phi = divisor - 1UL;
|
ulong phi = divisor - 1UL;
|
||||||
if (phi == 0UL)
|
// Divisors reach this helper only when they exceed the cached table, which means
|
||||||
{
|
// divisor >= 2 * exponent + 1 and phi cannot underflow to zero here.
|
||||||
return false;
|
// The defensive underflow guard is kept for posterity but disabled for the EvenPerfectBitScanner path.
|
||||||
}
|
// if (phi == 0UL)
|
||||||
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
int twoCount = BitOperations.TrailingZeroCount(phi);
|
int twoCount = BitOperations.TrailingZeroCount(phi);
|
||||||
ulong reducedPhi = phi >> twoCount;
|
ulong reducedPhi = phi >> twoCount;
|
||||||
if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
// Every divisor candidate generated by the by-divisor scan satisfies q ≡ 1 (mod 2 * exponent),
|
||||||
{
|
// ensuring reducedPhi keeps a factor of exponent and remains non-zero in this context.
|
||||||
return false;
|
// The divisibility check is therefore unreachable for the current caller and is commented out.
|
||||||
}
|
// if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
||||||
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong k = reducedPhi / exponent;
|
ulong k = reducedPhi / exponent;
|
||||||
if (k == 0UL)
|
// reducedPhi always contains at least one factor of exponent, so dividing by exponent leaves k >= 1.
|
||||||
{
|
// if (k == 0UL)
|
||||||
return false;
|
// {
|
||||||
}
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
Dictionary<ulong, int> factorCounts = new(8);
|
Dictionary<ulong, int> factorCounts = new(8);
|
||||||
if (twoCount > 0)
|
// The divisor candidates are odd, so phi is even and this branch always records at least one factor of two.
|
||||||
{
|
// if (twoCount > 0)
|
||||||
factorCounts[2UL] = twoCount;
|
// {
|
||||||
}
|
// factorCounts[2UL] = twoCount;
|
||||||
|
// }
|
||||||
|
factorCounts[2UL] = twoCount;
|
||||||
|
|
||||||
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
||||||
{
|
{
|
||||||
@@ -926,17 +942,22 @@ public class MersenneDivisorCycles
|
|||||||
|
|
||||||
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength)
|
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength)
|
||||||
{
|
{
|
||||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
// The by-divisor scanner only supplies odd candidates of the form 2*k*exponent + 1,
|
||||||
{
|
// so this power-of-two fast-path never triggers on that call path.
|
||||||
cycleLength = 1UL;
|
// The early exit is retained for reference but commented out because it is unreachable on the scanner path.
|
||||||
return true;
|
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||||
}
|
// {
|
||||||
|
// cycleLength = 1UL;
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
if (divisor <= 3UL)
|
// By the divisor-by-divisor pipeline, candidates have the shape 2 * exponent * k + 1 with exponent >= 5,
|
||||||
{
|
// so the divisor never drops below 11 on this call path.
|
||||||
cycleLength = CalculateCycleLengthFallback(divisor);
|
// if (divisor <= 3UL)
|
||||||
return true;
|
// {
|
||||||
}
|
// cycleLength = CalculateCycleLengthFallback(divisor);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -135,13 +135,16 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (prime <= 3UL)
|
// EvenPerfectBitScanner only reaches this helper with divisors of the form 2 * exponent * k + 1 where exponent >= 5,
|
||||||
{
|
// so the small-prime return path is unreachable in production.
|
||||||
return prime == 3UL ? 2UL : 1UL;
|
// if (prime <= 3UL)
|
||||||
}
|
// {
|
||||||
|
// return prime == 3UL ? 2UL : 1UL;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong phi = prime - 1UL;
|
ulong phi = prime - 1UL;
|
||||||
|
|
||||||
|
// The by-divisor scanner currently defaults to the CPU heuristic device, but --mersenne-device may request GPU heuristics.
|
||||||
if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(prime, previousOrder, config, divisorData, out ulong gpuOrder))
|
if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(prime, previousOrder, config, divisorData, out ulong gpuOrder))
|
||||||
{
|
{
|
||||||
return gpuOrder;
|
return gpuOrder;
|
||||||
@@ -150,12 +153,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
PartialFactorResult phiFactors = PartialFactor(phi, config);
|
PartialFactorResult phiFactors = PartialFactor(phi, config);
|
||||||
|
|
||||||
ulong result;
|
ulong result;
|
||||||
if (phiFactors.Factors is null)
|
// The by-divisor scan always yields phi = prime - 1 with at least the factor 2 captured during small-prime sieving,
|
||||||
{
|
// so PartialFactor never returns a null factor list on this call path.
|
||||||
result = FinishStrictlyCpu(prime, divisorData);
|
// if (phiFactors.Factors is null)
|
||||||
phiFactors.Dispose();
|
// {
|
||||||
return result;
|
// result = CalculateByFactorizationCpu(prime, divisorData);
|
||||||
}
|
// phiFactors.Dispose();
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
result = RunHeuristicPipelineCpu(prime, previousOrder, config, divisorData, phi, phiFactors);
|
result = RunHeuristicPipelineCpu(prime, previousOrder, config, divisorData, phi, phiFactors);
|
||||||
phiFactors.Dispose();
|
phiFactors.Dispose();
|
||||||
@@ -178,29 +183,37 @@ internal static partial class PrimeOrderCalculator
|
|||||||
ulong candidateOrder = InitializeStartingOrderCpu(prime, phi, divisorData);
|
ulong candidateOrder = InitializeStartingOrderCpu(prime, phi, divisorData);
|
||||||
candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData);
|
candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData);
|
||||||
|
|
||||||
if (TryConfirmOrderCpu(prime, candidateOrder, divisorData, config))
|
PartialFactorResult orderFactors = PartialFactor(candidateOrder, config);
|
||||||
|
if (orderFactors.Factors is null)
|
||||||
{
|
{
|
||||||
|
orderFactors.Dispose();
|
||||||
|
return candidateOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse the computed factorization for both the confirmation check and the heuristic pass
|
||||||
|
// to avoid factoring the same order twice on the by-divisor CPU pipeline.
|
||||||
|
if (TryConfirmOrderCpu(prime, candidateOrder, divisorData, ref orderFactors))
|
||||||
|
{
|
||||||
|
orderFactors.Dispose();
|
||||||
return candidateOrder;
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.Mode == PrimeOrderMode.Strict)
|
if (config.Mode == PrimeOrderMode.Strict)
|
||||||
{
|
{
|
||||||
return FinishStrictlyCpu(prime, divisorData);
|
orderFactors.Dispose();
|
||||||
|
return CalculateByFactorizationCpu(prime, divisorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryHeuristicFinishCpu(prime, candidateOrder, previousOrder, divisorData, config, phiFactors, out ulong order))
|
if (TryHeuristicFinishCpu(prime, candidateOrder, previousOrder, divisorData, config, ref orderFactors, phiFactors, out ulong order))
|
||||||
{
|
{
|
||||||
|
orderFactors.Dispose();
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orderFactors.Dispose();
|
||||||
return candidateOrder;
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong FinishStrictlyCpu(ulong prime, in MontgomeryDivisorData divisorData)
|
|
||||||
{
|
|
||||||
return CalculateByFactorizationCpu(prime, divisorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<FactorEntry> factorSpan = factors.Factors;
|
ReadOnlySpan<FactorEntry> factorSpan = factors.Factors;
|
||||||
@@ -297,12 +310,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConfirmOrderCpu(ulong prime, ulong order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
private static bool TryConfirmOrderCpu(ulong prime, ulong order, in MontgomeryDivisorData divisorData, ref PartialFactorResult orderFactors)
|
||||||
{
|
{
|
||||||
if (order == 0UL)
|
// The candidate order is always a positive divisor of phi in this flow, so zero never appears here.
|
||||||
{
|
// if (order == 0UL)
|
||||||
return false;
|
// {
|
||||||
}
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
// Calculating `a^order ≡ 1 (mod p)` is a prerequisite for `order` being the actual order of 2 modulo `p`.
|
// Calculating `a^order ≡ 1 (mod p)` is a prerequisite for `order` being the actual order of 2 modulo `p`.
|
||||||
// DebugLog("Verifying a^order ≡ 1 (mod p)");
|
// DebugLog("Verifying a^order ≡ 1 (mod p)");
|
||||||
@@ -311,66 +325,57 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLog("Partial factoring order");
|
// PartialFactor returns a null factor span when it cannot peel off any divisors, so keep the guard for composite candidate orders.
|
||||||
|
if (orderFactors.Factors is null)
|
||||||
// TODO: Do we do partial factoring of order multiple times?
|
|
||||||
PartialFactorResult factorization = PartialFactor(order, config);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (factorization.Factors is null)
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!orderFactors.FullyFactored)
|
||||||
|
{
|
||||||
|
// A leftover cofactor of 0 or 1 indicates the partial factorization produced no useful prime divisor.
|
||||||
|
if (orderFactors.Cofactor <= 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!factorization.FullyFactored)
|
// DebugLog("Cofactor > 1. Testing primality of cofactor");
|
||||||
|
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(orderFactors.Cofactor);
|
||||||
|
// bool isPrime = PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None);
|
||||||
|
if (!isPrime)
|
||||||
{
|
{
|
||||||
if (factorization.Cofactor <= 1UL)
|
return false;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugLog("Adding cofactor as prime factor");
|
|
||||||
PartialFactorResult extended = factorization.WithAdditionalPrime(factorization.Cofactor);
|
|
||||||
factorization.Dispose();
|
|
||||||
factorization = extended;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<FactorEntry> span = factorization.Factors;
|
// DebugLog("Adding cofactor as prime factor");
|
||||||
// DebugLog("Verifying prime-power reductions");
|
PartialFactorResult extended = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
||||||
int length = factorization.Count;
|
orderFactors.Dispose();
|
||||||
for (int i = 0; i < length; i++)
|
orderFactors = extended;
|
||||||
{
|
|
||||||
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 (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
ReadOnlySpan<FactorEntry> span = orderFactors.Factors!;
|
||||||
|
// DebugLog("Verifying prime-power reductions");
|
||||||
|
int length = orderFactors.Count;
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
factorization.Dispose();
|
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 (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryHeuristicFinishCpu(
|
private static bool TryHeuristicFinishCpu(
|
||||||
@@ -379,170 +384,164 @@ internal static partial class PrimeOrderCalculator
|
|||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
in PrimeOrderSearchConfig config,
|
in PrimeOrderSearchConfig config,
|
||||||
|
ref PartialFactorResult orderFactors,
|
||||||
PartialFactorResult phiFactors,
|
PartialFactorResult phiFactors,
|
||||||
out ulong result)
|
out ulong result)
|
||||||
{
|
{
|
||||||
result = 0UL;
|
result = 0UL;
|
||||||
if (order <= 1UL)
|
// The candidate order stays at least 2 for odd divisors above 3, so this guard never triggers in the EvenPerfectBitScanner flow.
|
||||||
|
// if (order <= 1UL)
|
||||||
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// PartialFactor returns a null factor span when it cannot peel off any divisors, so the heuristic pass must bail in that case.
|
||||||
|
if (orderFactors.Factors is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Do we do partial factoring of order multiple times?
|
if (!orderFactors.FullyFactored)
|
||||||
// DebugLog("Trying heuristic. Partial factoring order");
|
|
||||||
PartialFactorResult orderFactors = PartialFactor(order, config);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (orderFactors.Factors is null)
|
// The partial factoring helper reports a cofactor of 0 or 1 when nothing usable remains, so short-circuit the heuristic stage.
|
||||||
|
if (orderFactors.Cofactor <= 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!orderFactors.FullyFactored)
|
var isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(orderFactors.Cofactor);
|
||||||
{
|
// if (!PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None))
|
||||||
if (orderFactors.Cofactor <= 1UL)
|
if (!isPrime)
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(orderFactors.Cofactor);
|
|
||||||
// if (!PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None))
|
|
||||||
if (!isPrime)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLog("Sorting candidates");
|
PartialFactorResult extended = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
||||||
SortCandidates(prime, previousOrder, candidates);
|
orderFactors.Dispose();
|
||||||
|
orderFactors = extended;
|
||||||
|
}
|
||||||
|
|
||||||
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
|
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
|
||||||
int powUsed = 0;
|
List<ulong> candidates = new(capacity);
|
||||||
int candidateCount = candidates.Count;
|
FactorEntry[] factorArray = orderFactors.Factors!;
|
||||||
bool allowGpuBatch = true;
|
// DebugLog("Building candidates list");
|
||||||
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
BuildCandidates(order, factorArray, orderFactors.Count, candidates, capacity);
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)");
|
// DebugLog("Sorting candidates");
|
||||||
int index = 0;
|
SortCandidates(prime, previousOrder, candidates);
|
||||||
const int MaxGpuBatchSize = 1024;
|
|
||||||
const int StackGpuBatchSize = 1024;
|
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
|
||||||
Span<ulong> stackGpuRemainders = stackalloc ulong[StackGpuBatchSize];
|
int powUsed = 0;
|
||||||
ArrayPool<ulong> uLongPool = ULongPool;
|
int candidateCount = candidates.Count;
|
||||||
while (index < candidateCount && powUsed < powBudget)
|
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];
|
||||||
|
ArrayPool<ulong> uLongPool = ULongPool;
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
int remaining = candidateCount - index;
|
break;
|
||||||
int budgetRemaining = powBudget - powUsed;
|
}
|
||||||
int batchSize = Math.Min(remaining, Math.Min(budgetRemaining, MaxGpuBatchSize));
|
|
||||||
if (batchSize <= 0)
|
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)
|
||||||
{
|
{
|
||||||
break;
|
Span<ulong> localRemainders = stackGpuRemainders.Slice(0, batchSize);
|
||||||
}
|
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, localRemainders, divisorData);
|
||||||
|
if (status == GpuPow2ModStatus.Success)
|
||||||
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);
|
gpuSuccess = true;
|
||||||
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, localRemainders, divisorData);
|
gpuStackRemainders = true;
|
||||||
if (status == GpuPow2ModStatus.Success)
|
|
||||||
{
|
|
||||||
gpuSuccess = true;
|
|
||||||
gpuStackRemainders = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gpuPool = uLongPool.Rent(batchSize);
|
|
||||||
Span<ulong> pooledRemainders = gpuPool.AsSpan(0, batchSize);
|
|
||||||
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, pooledRemainders, divisorData);
|
|
||||||
if (status == GpuPow2ModStatus.Success)
|
|
||||||
{
|
|
||||||
pooledGpuRemainders = pooledRemainders;
|
|
||||||
gpuSuccess = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uLongPool.Return(gpuPool, clearArray: false);
|
|
||||||
gpuPool = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gpuSuccess && (status == GpuPow2ModStatus.Overflow || status == GpuPow2ModStatus.Unavailable))
|
|
||||||
{
|
|
||||||
allowGpuBatch = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
for (int i = 0; i < batchSize && powUsed < powBudget; i++)
|
|
||||||
{
|
{
|
||||||
ulong candidate = batch[i];
|
gpuPool = uLongPool.Rent(batchSize);
|
||||||
powUsed++;
|
Span<ulong> pooledRemainders = gpuPool.AsSpan(0, batchSize);
|
||||||
|
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, pooledRemainders, divisorData);
|
||||||
bool equalsOne;
|
if (status == GpuPow2ModStatus.Success)
|
||||||
if (gpuSuccess)
|
|
||||||
{
|
{
|
||||||
ulong remainderValue = gpuStackRemainders ? stackGpuRemainders[i] : pooledGpuRemainders[i];
|
pooledGpuRemainders = pooledRemainders;
|
||||||
equalsOne = remainderValue == 1UL;
|
gpuSuccess = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
equalsOne = Pow2EqualsOneCpu(candidate, prime, divisorData);
|
|
||||||
}
|
|
||||||
if (!equalsOne)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryConfirmCandidateCpu(prime, candidate, divisorData, config, ref powUsed, powBudget))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gpuPool is not null)
|
|
||||||
{
|
{
|
||||||
uLongPool.Return(gpuPool, clearArray: false);
|
uLongPool.Return(gpuPool, clearArray: false);
|
||||||
|
gpuPool = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result = candidate;
|
if (!gpuSuccess && (status == GpuPow2ModStatus.Overflow || status == GpuPow2ModStatus.Unavailable))
|
||||||
return true;
|
{
|
||||||
|
allowGpuBatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < batchSize && powUsed < powBudget; i++)
|
||||||
|
{
|
||||||
|
ulong candidate = batch[i];
|
||||||
|
powUsed++;
|
||||||
|
|
||||||
|
bool equalsOne;
|
||||||
|
if (gpuSuccess)
|
||||||
|
{
|
||||||
|
ulong remainderValue = gpuStackRemainders ? stackGpuRemainders[i] : pooledGpuRemainders[i];
|
||||||
|
equalsOne = remainderValue == 1UL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
equalsOne = Pow2EqualsOneCpu(candidate, prime, divisorData);
|
||||||
|
}
|
||||||
|
if (!equalsOne)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryConfirmCandidateCpu(prime, candidate, divisorData, config, ref powUsed, powBudget))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gpuPool is not null)
|
if (gpuPool is not null)
|
||||||
{
|
{
|
||||||
uLongPool.Return(gpuPool, clearArray: false);
|
uLongPool.Return(gpuPool, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
index += batchSize;
|
result = candidate;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLog("No candidate confirmed");
|
if (gpuPool is not null)
|
||||||
return false;
|
{
|
||||||
}
|
uLongPool.Return(gpuPool, clearArray: false);
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
orderFactors.Dispose();
|
index += batchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|||||||
Reference in new issue
Block a user