Hybrid device mode implemented as the current --mersenne=bydivisor CPU execution path. CPU path is now limited to CPU calculations, only.

This commit is contained in:
mlemanczyk committed 2025-12-01 03:45:39 +01:00
1 parent 353a121d03
commit 941733905b
49 files changed
+4965 -1726

No files matched your search

@@ -2,11 +2,24 @@ using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu;
internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelerator gpu) : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelerator gpu, ComputationDevice orderDevice) : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{
public void Reset()
{
}
private Func<ulong, ulong> _getCycleLength = orderDevice switch
{
ComputationDevice.Gpu => divisor => DivisorCycleCache.Shared.GetCycleLengthGpu(gpu, divisor),
ComputationDevice.Hybrid => divisor => DivisorCycleCache.Shared.GetCycleLengthHybrid(gpu, divisor),
_ => static (divisor) => DivisorCycleCache.Shared.GetCycleLengthCpu(divisor),
};
public void Configure(PrimeOrderCalculatorAccelerator gpu, ComputationDevice orderDevice)
{
_getCycleLength = orderDevice switch
{
ComputationDevice.Gpu => divisor => DivisorCycleCache.Shared.GetCycleLengthGpu(gpu, divisor),
ComputationDevice.Hybrid => divisor => DivisorCycleCache.Shared.GetCycleLengthHybrid(gpu, divisor),
_ => static (divisor) => DivisorCycleCache.Shared.GetCycleLengthCpu(divisor),
};
}
public void CheckDivisor(
ulong divisor,
@@ -29,15 +42,15 @@ internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelera
// cachedData = MontgomeryDivisorData.FromModulus(divisor);
}
if (divisorCycle == 0UL)
{
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(gpu, divisor);
if (divisorCycle == 0UL)
{
// DivisorCycleCache guarantees a positive cycle for divisors greater than one.
throw new InvalidOperationException($"Divisor cycle solver returned zero for divisor {divisor}.");
}
}
if (divisorCycle == 0UL)
{
divisorCycle = _getCycleLength(divisor);
if (divisorCycle == 0UL)
{
// DivisorCycleCache guarantees a positive cycle for divisors greater than one.
throw new InvalidOperationException($"Divisor cycle solver returned zero for divisor {divisor}.");
}
}
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
@@ -4,12 +4,40 @@ using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberDivisorByDivisorTester
public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice orderDevice = ComputationDevice.Hybrid) : IMersenneNumberDivisorByDivisorTester
{
private ulong _divisorLimit;
private int _batchSize = 1_024;
private ulong _minK = 1UL;
private readonly TryCycleLengthDelegate _tryCalculateCycleLengthForExponent = orderDevice switch
{
ComputationDevice.Gpu => TryCalculateCycleLengthForExponentGpu,
ComputationDevice.Hybrid => TryCalculateCycleLengthForExponentHybrid,
_ => TryCalculateCycleLengthForExponentCpu,
};
private readonly CalculateCycleLengthDelegate _calculateCycleLength = orderDevice switch
{
ComputationDevice.Gpu => CalculateCycleLengthGpu,
ComputationDevice.Hybrid => CalculateCycleLengthHybrid,
_ => CalculateCycleLengthCpu,
};
private delegate bool TryCycleLengthDelegate(
ulong divisor,
PrimeOrderCalculatorAccelerator gpu,
ulong exponent,
in MontgomeryDivisorData divisorData,
out ulong cycleLength,
out bool primeOrderFailed);
private delegate ulong CalculateCycleLengthDelegate(
ulong divisor,
PrimeOrderCalculatorAccelerator gpu,
in MontgomeryDivisorData divisorData,
bool skipPrimeOrderHeuristic);
[ThreadStatic]
private static GpuUInt128WorkSet _divisorScanGpuWorkSet;
@@ -96,15 +124,17 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberD
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
{
MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession();
if (session is not null)
{
session.Configure(gpu, orderDevice);
return session;
}
return new MersenneCpuDivisorScanSession(gpu);
return new MersenneCpuDivisorScanSession(gpu, orderDevice);
}
private static bool CheckDivisors(
private bool CheckDivisors(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
ulong allowedMax,
@@ -269,9 +299,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberD
MontgomeryDivisorData divisorData = divisorPool.FromModulus(candidate);
ulong divisorCycle;
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
gpu,
if (!_tryCalculateCycleLengthForExponent(
candidate,
gpu,
prime,
divisorData,
out ulong computedCycle,
@@ -279,9 +309,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberD
{
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
gpu,
divisorCycle = _calculateCycleLength(
candidate,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
@@ -319,7 +349,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberD
return false;
}
private static bool CheckDivisors64Range(
private bool CheckDivisors64Range(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
ulong step,
@@ -388,7 +418,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberD
out foundDivisor);
}
private static bool CheckDivisors64(
private bool CheckDivisors64(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
ulong step,
@@ -418,17 +448,17 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberD
{
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
ulong divisorCycle;
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
gpu,
if (!_tryCalculateCycleLengthForExponent(
divisor,
gpu,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
gpu,
divisorCycle = _calculateCycleLength(
divisor,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
@@ -473,17 +503,17 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberD
{
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
ulong divisorCycle;
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
gpu,
if (!_tryCalculateCycleLengthForExponent(
divisor,
gpu,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
gpu,
divisorCycle = _calculateCycleLength(
divisor,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
@@ -525,10 +555,80 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberD
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
{
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
ulong residue = prime.Pow2MontgomeryModWithCycleConvertToStandardCpu(divisorCycle, divisorData);
return residue == 1UL ? (byte)1 : (byte)0;
}
private static bool TryCalculateCycleLengthForExponentCpu(
ulong divisor,
PrimeOrderCalculatorAccelerator gpu,
ulong exponent,
in MontgomeryDivisorData divisorData,
out ulong cycleLength,
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
divisor,
exponent,
divisorData,
out cycleLength,
out primeOrderFailed);
private static bool TryCalculateCycleLengthForExponentHybrid(
ulong divisor,
PrimeOrderCalculatorAccelerator gpu,
ulong exponent,
in MontgomeryDivisorData divisorData,
out ulong cycleLength,
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentHybrid(
gpu,
divisor,
exponent,
divisorData,
out cycleLength,
out primeOrderFailed);
private static bool TryCalculateCycleLengthForExponentGpu(
ulong divisor,
PrimeOrderCalculatorAccelerator gpu,
ulong exponent,
in MontgomeryDivisorData divisorData,
out ulong cycleLength,
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentGpu(
gpu,
divisor,
exponent,
divisorData,
out cycleLength,
out primeOrderFailed);
private static ulong CalculateCycleLengthCpu(
ulong divisor,
PrimeOrderCalculatorAccelerator gpu,
in MontgomeryDivisorData divisorData,
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthCpu(
divisor,
divisorData,
skipPrimeOrderHeuristic);
private static ulong CalculateCycleLengthHybrid(
ulong divisor,
PrimeOrderCalculatorAccelerator gpu,
in MontgomeryDivisorData divisorData,
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthHybrid(
gpu,
divisor,
divisorData,
skipPrimeOrderHeuristic);
private static ulong CalculateCycleLengthGpu(
ulong divisor,
PrimeOrderCalculatorAccelerator gpu,
in MontgomeryDivisorData divisorData,
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthGpu(
gpu,
divisor,
divisorData,
skipPrimeOrderHeuristic);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod3(byte value, byte delta)
{
@@ -31,7 +31,7 @@ public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType)
{
// TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block
// and compute missing cycles on the configured device without queuing additional block generation.
qCycle = MersenneDivisorCycles.GetCycle(gpu, q);
qCycle = MersenneDivisorCycles.GetCycleCpu(q);
shouldCheck = auto.Mod10R != 5UL;
if (shouldCheck)
{
@@ -22,7 +22,7 @@ public class MersenneNumberLucasLehmerCpuTester
return false;
}
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu))
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOneCpu())
{
return false;
}
@@ -34,7 +34,7 @@ public class MersenneNumberLucasLehmerCpuTester
for (UInt128 i = UInt128.Zero; i < limit; i++)
{
// Lucas–Lehmer squares the evolving residue, so we stay on the Montgomery powmod path rather than the pow2-specific helper.
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(gpu, m)) - two;
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycleCpu(m)) - two;
if (s < UInt128.Zero)
{
s += m;
@@ -15,7 +15,7 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
while (k <= maxK && Volatile.Read(ref isPrime))
{
qCycle = MersenneDivisorCycles.GetCycle(gpu, q);
qCycle = MersenneDivisorCycles.GetCycleCpu(q);
// TODO: When this lookup misses the snapshot, invoke the configured device to compute the
// single required cycle on demand without persisting it so the CPU order path retains the
// cycle-stepping speedups while honoring the no-extra-cache constraint for large divisors.
@@ -7,7 +7,7 @@ public class MersenneNumberResidueCpuTester
private ModResidueTracker? _mersenneResidueTracker;
// CPU residue variant using tracker + unrolled residue updates (no method calls in the loop).
public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
// Initialize/update Mersenne residue tracker and start a merge walk for ascending q divisors
@@ -94,7 +94,7 @@ public class MersenneNumberResidueCpuTester
// (and below) so every qualifying q reuses precomputed lengths instead of recomputing via
// tracker.MergeOrAppend.
// cycle-based quick check for small q
if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, q, out isDivider) && isDivider &&
if (localIsPrime && tracker.MergeOrAppendCpu(exponent, q, out isDivider) && isDivider &&
(q <= ulong.MaxValue ? ((ulong)q).IsPrimeCandidate() : q.IsPrimeCandidate()))
{
localIsPrime = false;
@@ -107,7 +107,7 @@ public class MersenneNumberResidueCpuTester
((allowMask >> (int)r10_1) & 1) != 0 && (r8_1 == 1UL || r8_1 == 7UL) && r3_1 != 0UL && r5_1 != 0UL &&
localIsPrime)
{
if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider &&
if (localIsPrime && tracker.MergeOrAppendCpu(exponent, qIncremental, out isDivider) && isDivider &&
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
{
localIsPrime = false;
@@ -120,7 +120,7 @@ public class MersenneNumberResidueCpuTester
((allowMask >> (int)r10_2) & 1) != 0 && (r8_2 == 1UL || r8_2 == 7UL) && r3_2 != 0UL && r5_2 != 0UL &&
localIsPrime)
{
if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider &&
if (localIsPrime && tracker.MergeOrAppendCpu(exponent, qIncremental, out isDivider) && isDivider &&
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
{
localIsPrime = false;
@@ -133,7 +133,7 @@ public class MersenneNumberResidueCpuTester
((allowMask >> (int)r10_3) & 1) != 0 && (r8_3 == 1UL || r8_3 == 7UL) && r3_3 != 0UL && r5_3 != 0UL &&
localIsPrime)
{
if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider &&
if (localIsPrime && tracker.MergeOrAppendCpu(exponent, qIncremental, out isDivider) && isDivider &&
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
{
localIsPrime = false;