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:
1 parent
353a121d03
commit
941733905b
49 files changed
+4965
-1726
No files matched your search
@@ -0,0 +1,8 @@
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
public enum ComputationDevice
|
||||
{
|
||||
Cpu,
|
||||
Hybrid,
|
||||
Gpu,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -15,7 +15,6 @@ public sealed class DivisorCycleCache
|
||||
private readonly int _acceleratorIndex;
|
||||
private readonly Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> _gpuKernel;
|
||||
private volatile ulong[] _snapshot;
|
||||
private volatile bool _useGpuGeneration = true;
|
||||
private readonly int _divisorCyclesBatchSize;
|
||||
private static int _sharedDivisorCyclesBatchSize = GpuConstants.GpuCycleStepsPerInvocation;
|
||||
|
||||
@@ -47,23 +46,18 @@ public sealed class DivisorCycleCache
|
||||
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
}
|
||||
|
||||
public void ConfigureGeneratorDevice(bool useGpu)
|
||||
{
|
||||
_useGpuGeneration = useGpu;
|
||||
}
|
||||
|
||||
public void RefreshSnapshot()
|
||||
{
|
||||
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
}
|
||||
|
||||
public ulong GetCycleLength(PrimeOrderCalculatorAccelerator gpu, ulong divisor, bool skipPrimeOrderHeuristic = false)
|
||||
public ulong GetCycleLengthCpu(ulong divisor, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (!skipPrimeOrderHeuristic)
|
||||
{
|
||||
Span<ulong> result = stackalloc ulong[1];
|
||||
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
|
||||
GetCycleLengths(gpu, singleDivisor, result);
|
||||
GetCycleLengthsCpu(singleDivisor, result);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
@@ -90,12 +84,88 @@ public sealed class DivisorCycleCache
|
||||
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
|
||||
cycleLength = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
cycleLength = MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
divisorPool.Return(divisorData);
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
public void GetCycleLengths(PrimeOrderCalculatorAccelerator gpu,ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
|
||||
public ulong GetCycleLengthHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (!skipPrimeOrderHeuristic)
|
||||
{
|
||||
Span<ulong> result = stackalloc ulong[1];
|
||||
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
|
||||
GetCycleLengthsHybrid(gpu, singleDivisor, result);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
// The skipPrimeOrderHeuristic path is only used for divisors produced by the CPU by-divisor scanner,
|
||||
// which always generates values greater than one. Keep this guard disabled here to avoid repeating the
|
||||
// validation already performed by the general GetCycleLengths path.
|
||||
// if (divisor <= 1UL)
|
||||
// {
|
||||
// throw new InvalidDataException("Divisor must be > 1");
|
||||
// }
|
||||
|
||||
ulong[] snapshot = _snapshot;
|
||||
ulong cycleLength;
|
||||
if (divisor < (ulong)snapshot.Length)
|
||||
{
|
||||
cycleLength = snapshot[divisor];
|
||||
if (cycleLength == 0UL)
|
||||
{
|
||||
throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
|
||||
}
|
||||
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
|
||||
cycleLength = MersenneDivisorCycles.CalculateCycleLengthHybrid(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
divisorPool.Return(divisorData);
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
public ulong GetCycleLengthGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (!skipPrimeOrderHeuristic)
|
||||
{
|
||||
Span<ulong> result = stackalloc ulong[1];
|
||||
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
|
||||
GetCycleLengthsGpu(singleDivisor, result);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
// The skipPrimeOrderHeuristic path is only used for divisors produced by the CPU by-divisor scanner,
|
||||
// which always generates values greater than one. Keep this guard disabled here to avoid repeating the
|
||||
// validation already performed by the general GetCycleLengths path.
|
||||
// if (divisor <= 1UL)
|
||||
// {
|
||||
// throw new InvalidDataException("Divisor must be > 1");
|
||||
// }
|
||||
|
||||
ulong[] snapshot = _snapshot;
|
||||
ulong cycleLength;
|
||||
if (divisor < (ulong)snapshot.Length)
|
||||
{
|
||||
cycleLength = snapshot[divisor];
|
||||
if (cycleLength == 0UL)
|
||||
{
|
||||
throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
|
||||
}
|
||||
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
|
||||
cycleLength = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
divisorPool.Return(divisorData);
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
public void GetCycleLengthsCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
|
||||
{
|
||||
ulong[] snapshot = _snapshot;
|
||||
int length = divisors.Length;
|
||||
@@ -133,16 +203,103 @@ public sealed class DivisorCycleCache
|
||||
if (missingCount != 0)
|
||||
{
|
||||
ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount];
|
||||
if (_useGpuGeneration)
|
||||
ComputeCyclesCpu(divisors, cycles, missingIndices);
|
||||
}
|
||||
|
||||
if (rentedMissing is not null)
|
||||
{
|
||||
ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetCycleLengthsHybrid(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
|
||||
{
|
||||
ulong[] snapshot = _snapshot;
|
||||
int length = divisors.Length;
|
||||
int[]? rentedMissing = null;
|
||||
Span<int> missingBuffer = length <= StackBufferThreshold
|
||||
? stackalloc int[length]
|
||||
: new Span<int>(rentedMissing = ThreadStaticPools.IntPool.Rent(length), 0, length);
|
||||
|
||||
int missingCount = 0;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong divisor = divisors[i];
|
||||
if (divisor <= 1UL)
|
||||
{
|
||||
ComputeCyclesGpu(divisors, cycles, missingIndices);
|
||||
throw new InvalidDataException("Divisor must be > 1");
|
||||
}
|
||||
|
||||
if (divisor < (ulong)snapshot.Length)
|
||||
{
|
||||
ulong cached = snapshot[divisor];
|
||||
if (cached == 0UL)
|
||||
{
|
||||
throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
|
||||
}
|
||||
|
||||
cycles[i] = cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
ComputeCyclesCpu(gpu, divisors, cycles, missingIndices);
|
||||
missingBuffer[missingCount++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (missingCount != 0)
|
||||
{
|
||||
ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount];
|
||||
ComputeCyclesHybrid(gpu, divisors, cycles, missingIndices);
|
||||
}
|
||||
|
||||
if (rentedMissing is not null)
|
||||
{
|
||||
ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetCycleLengthsGpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
|
||||
{
|
||||
ulong[] snapshot = _snapshot;
|
||||
int length = divisors.Length;
|
||||
int[]? rentedMissing = null;
|
||||
Span<int> missingBuffer = length <= StackBufferThreshold
|
||||
? stackalloc int[length]
|
||||
: new Span<int>(rentedMissing = ThreadStaticPools.IntPool.Rent(length), 0, length);
|
||||
|
||||
int missingCount = 0;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong divisor = divisors[i];
|
||||
if (divisor <= 1UL)
|
||||
{
|
||||
throw new InvalidDataException("Divisor must be > 1");
|
||||
}
|
||||
|
||||
if (divisor < (ulong)snapshot.Length)
|
||||
{
|
||||
ulong cached = snapshot[divisor];
|
||||
if (cached == 0UL)
|
||||
{
|
||||
throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
|
||||
}
|
||||
|
||||
cycles[i] = cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
missingBuffer[missingCount++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (missingCount != 0)
|
||||
{
|
||||
ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount];
|
||||
ComputeCyclesGpu(divisors, cycles, missingIndices);
|
||||
}
|
||||
|
||||
if (rentedMissing is not null)
|
||||
{
|
||||
ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false);
|
||||
@@ -187,7 +344,7 @@ public sealed class DivisorCycleCache
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeCyclesCpu(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
{
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
@@ -195,7 +352,21 @@ public sealed class DivisorCycleCache
|
||||
int targetIndex = indices[i];
|
||||
ulong divisor = divisors[targetIndex];
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData);
|
||||
divisorPool.Return(divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeCyclesHybrid(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
{
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
int targetIndex = indices[i];
|
||||
ulong divisor = divisors[targetIndex];
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLengthHybrid(gpu, divisor, divisorData);
|
||||
divisorPool.Return(divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,16 +23,14 @@ internal struct ExponentRemainderStepperCpu
|
||||
_currentMontgomery = divisor.MontgomeryOne;
|
||||
}
|
||||
|
||||
public bool MatchesDivisor(in MontgomeryDivisorData divisor)
|
||||
{
|
||||
return _modulus == divisor.Modulus && _nPrime == divisor.NPrime && _montgomeryOne == divisor.MontgomeryOne;
|
||||
}
|
||||
public readonly bool MatchesDivisor(in MontgomeryDivisorData divisor)
|
||||
=> _modulus == divisor.Modulus && _nPrime == divisor.NPrime && _montgomeryOne == divisor.MontgomeryOne;
|
||||
|
||||
/// <summary>
|
||||
/// Clears the cached Montgomery residue so the stepper can be reused for another divisor.
|
||||
/// Callers must reinitialize the stepper before consuming any residues after a reset.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
/// <summary>
|
||||
/// Clears the cached Montgomery residue so the stepper can be reused for another divisor.
|
||||
/// Callers must reinitialize the stepper before consuming any residues after a reset.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
PreviousExponent = 0UL;
|
||||
_currentMontgomery = _montgomeryOne;
|
||||
@@ -73,7 +71,7 @@ internal struct ExponentRemainderStepperCpu
|
||||
// TODO: Once divisor cycle lengths are mandatory, pull the delta multiplier from the
|
||||
// single-block divisor-cycle snapshot so we can skip the powmod entirely and reuse the
|
||||
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
||||
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
ulong multiplier = delta.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime);
|
||||
PreviousExponent = exponent;
|
||||
return ReduceCurrent();
|
||||
@@ -91,7 +89,7 @@ internal struct ExponentRemainderStepperCpu
|
||||
ulong delta = exponent - PreviousExponent;
|
||||
// TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle
|
||||
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
||||
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
ulong multiplier = delta.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime);
|
||||
PreviousExponent = exponent;
|
||||
return _currentMontgomery == _montgomeryOne;
|
||||
@@ -134,9 +132,9 @@ internal struct ExponentRemainderStepperCpu
|
||||
|
||||
private void InitializeCpuState(ulong exponent)
|
||||
{
|
||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor);
|
||||
PreviousExponent = exponent;
|
||||
}
|
||||
|
||||
private ulong ReduceCurrent() => _currentMontgomery.MontgomeryMultiplyCpu(1UL, _modulus, _nPrime);
|
||||
private readonly ulong ReduceCurrent() => _currentMontgomery.MontgomeryMultiplyCpu(1UL, _modulus, _nPrime);
|
||||
}
|
||||
@@ -511,13 +511,12 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
return composite;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ResolveDivisorCycle(PrimeOrderCalculatorAccelerator gpu, ulong divisor, ulong prime, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentGpu(gpu, divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
return MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
return MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
|
||||
return computedCycle;
|
||||
@@ -729,9 +728,9 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
var gpu = _primeOrderCalculatorAccelerator;
|
||||
if (cycle == 0UL)
|
||||
{
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -125,7 +125,6 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
}
|
||||
|
||||
UInt128 kMul2;
|
||||
var divisorCycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
|
||||
ulong limit = divisorCyclesSearchLimit;
|
||||
if ((UInt128)limit > maxK2)
|
||||
@@ -143,7 +142,7 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
continue;
|
||||
}
|
||||
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(gpu, d);
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycleGpu(gpu, d);
|
||||
if ((UInt128)p % cycle128 != UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -143,7 +143,7 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu))
|
||||
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOneGpu(gpu))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -21,18 +22,16 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
|
||||
// TODO: When cycles are missing for larger q values, compute only the single required cycle on the device
|
||||
// requested by the caller, skip queuing additional block generation, and keep the snapshot cache untouched.
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
// GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1.
|
||||
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
|
||||
public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
// GpuPrimeWorkLimiter.Acquire();
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var acceleratorIndex = gpu.AcceleratorIndex;
|
||||
var accelerator = gpu.Accelerator;
|
||||
|
||||
// Monitor.Enter(gpuLease.ExecutionLock);
|
||||
|
||||
var stream = accelerator.CreateStream();
|
||||
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
var kernel = GetPow2ModKernel(accelerator);
|
||||
var resources = RentResources(accelerator, GpuConstants.ScanBatchSize);
|
||||
var gpuKernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.SmallCycles | KernelType.SmallPrimes);
|
||||
@@ -109,7 +108,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
|
||||
}
|
||||
|
||||
ReturnResources(resources);
|
||||
stream.Dispose();
|
||||
AcceleratorStreamPool.Return(acceleratorIndex, stream);
|
||||
// Monitor.Exit(gpuLease.ExecutionLock);
|
||||
gpuKernels.Dispose();
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
|
||||
@@ -248,18 +248,9 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
{
|
||||
order = 0UL;
|
||||
|
||||
// GpuPrimeWorkLimiter.Acquire();
|
||||
int acceleratorIndex = gpu.AcceleratorIndex;
|
||||
var smallPrimesView = gpu.SmallPrimeFactorPrimes;
|
||||
var smallSquaresView = gpu.SmallPrimeFactorSquares;
|
||||
// var phiFactorBufferView = gpu.PhiFactorBufferView;
|
||||
// var phiExponentBufferView = gpu.PhiExponentBufferView;
|
||||
// var workFactorBufferView = gpu.WorkFactorBufferView;
|
||||
// var workExponentBufferView = gpu.WorkExponentBufferView;
|
||||
// var candidateBufferView = gpu.CandidateBufferView;
|
||||
// var stackIndexBufferView = gpu.StackIndexBufferView;
|
||||
// var stackExponentBufferView = gpu.StackExponentBufferView;
|
||||
// var stackProductBufferView = gpu.StackProductBufferView;
|
||||
var resultBufferView = gpu.OutputUlongView2;
|
||||
var statusBufferView = gpu.OutputByteView;
|
||||
|
||||
@@ -274,14 +265,6 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
|
||||
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
// TODO: Remove the cleaning after the order kernel is modified to always set the result.
|
||||
// phiFactorBufferView.MemSetToZero(stream);
|
||||
// phiExponentBufferView.MemSetToZero(stream);
|
||||
// workFactorBufferView.MemSetToZero(stream);
|
||||
// workExponentBufferView.MemSetToZero(stream);
|
||||
// candidateBufferView.MemSetToZero(stream);
|
||||
// stackIndexBufferView.MemSetToZero(stream);
|
||||
// stackExponentBufferView.MemSetToZero(stream);
|
||||
// stackProductBufferView.MemSetToZero(stream);
|
||||
resultBufferView.MemSetToZero(stream);
|
||||
statusBufferView.MemSetToZero(stream);
|
||||
|
||||
@@ -307,7 +290,6 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
stream.Synchronize();
|
||||
|
||||
AcceleratorStreamPool.Return(acceleratorIndex, stream);
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
|
||||
PrimeOrderKernelStatus kernelStatus = (PrimeOrderKernelStatus)status;
|
||||
if (kernelStatus == PrimeOrderKernelStatus.Fallback)
|
||||
|
||||
@@ -479,7 +479,48 @@ public sealed class HeuristicCombinedPrimeTester
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong ResolveHeuristicCycleLength(
|
||||
internal static ulong ResolveHeuristicCycleLengthCpu(
|
||||
ulong exponent,
|
||||
in HeuristicDivisorPreparation preparation,
|
||||
out bool cycleFromHint,
|
||||
out bool cycleComputed,
|
||||
out bool primeOrderFailed)
|
||||
{
|
||||
if (preparation.HasCycleLengthHint && preparation.CycleLengthHint != 0UL)
|
||||
{
|
||||
cycleFromHint = true;
|
||||
cycleComputed = true;
|
||||
primeOrderFailed = false;
|
||||
return preparation.CycleLengthHint;
|
||||
}
|
||||
|
||||
ulong divisor = preparation.Candidate.Value;
|
||||
MontgomeryDivisorData divisorData = preparation.DivisorData;
|
||||
|
||||
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailedLocal);
|
||||
|
||||
if (trySuccess && computedCycle != 0UL)
|
||||
{
|
||||
cycleFromHint = false;
|
||||
cycleComputed = true;
|
||||
primeOrderFailed = primeOrderFailedLocal;
|
||||
return computedCycle;
|
||||
}
|
||||
|
||||
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycleFromHint = false;
|
||||
cycleComputed = resolvedCycle != 0UL;
|
||||
return resolvedCycle;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong ResolveHeuristicCycleLengthGpu(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong exponent,
|
||||
in HeuristicDivisorPreparation preparation,
|
||||
@@ -499,7 +540,6 @@ public sealed class HeuristicCombinedPrimeTester
|
||||
MontgomeryDivisorData divisorData = preparation.DivisorData;
|
||||
|
||||
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
gpu,
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
@@ -515,7 +555,7 @@ public sealed class HeuristicCombinedPrimeTester
|
||||
}
|
||||
|
||||
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycleFromHint = false;
|
||||
cycleComputed = resolvedCycle != 0UL;
|
||||
return resolvedCycle;
|
||||
|
||||
@@ -449,7 +449,48 @@ public sealed class HeuristicPrimeTester
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong ResolveHeuristicCycleLength(
|
||||
internal static ulong ResolveHeuristicCycleLengthCpu(
|
||||
ulong exponent,
|
||||
in HeuristicDivisorPreparation preparation,
|
||||
out bool cycleFromHint,
|
||||
out bool cycleComputed,
|
||||
out bool primeOrderFailed)
|
||||
{
|
||||
if (preparation.HasCycleLengthHint && preparation.CycleLengthHint != 0UL)
|
||||
{
|
||||
cycleFromHint = true;
|
||||
cycleComputed = true;
|
||||
primeOrderFailed = false;
|
||||
return preparation.CycleLengthHint;
|
||||
}
|
||||
|
||||
ulong divisor = preparation.Candidate.Value;
|
||||
MontgomeryDivisorData divisorData = preparation.DivisorData;
|
||||
|
||||
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailedLocal);
|
||||
|
||||
if (trySuccess && computedCycle != 0UL)
|
||||
{
|
||||
cycleFromHint = false;
|
||||
cycleComputed = true;
|
||||
primeOrderFailed = primeOrderFailedLocal;
|
||||
return computedCycle;
|
||||
}
|
||||
|
||||
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycleFromHint = false;
|
||||
cycleComputed = resolvedCycle != 0UL;
|
||||
return resolvedCycle;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong ResolveHeuristicCycleLengthGpu(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong exponent,
|
||||
in HeuristicDivisorPreparation preparation,
|
||||
@@ -469,7 +510,6 @@ public sealed class HeuristicPrimeTester
|
||||
MontgomeryDivisorData divisorData = preparation.DivisorData;
|
||||
|
||||
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
gpu,
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
@@ -485,7 +525,7 @@ public sealed class HeuristicPrimeTester
|
||||
}
|
||||
|
||||
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycleFromHint = false;
|
||||
cycleComputed = resolvedCycle != 0UL;
|
||||
return resolvedCycle;
|
||||
|
||||
@@ -75,17 +75,93 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthHeuristic(gpu, divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
||||
if (TryCalculateCycleLengthHeuristicCpu(divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
||||
{
|
||||
return heuristicCycle == exponent;
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthForExponentCpu(gpu, divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
if (TryCalculateCycleLengthForExponentCpu(divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
{
|
||||
return cycleLength == exponent;
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||
return exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisorData) == 1UL;
|
||||
}
|
||||
|
||||
public static bool CycleEqualsExponentGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||
{
|
||||
if (CycleEqualsExponentForMersenneCandidate(divisor, divisorData, exponent))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
var small = Shared._smallCycles;
|
||||
if (small is not null)
|
||||
{
|
||||
ulong cached = small[(int)divisor];
|
||||
if (cached != 0UL)
|
||||
{
|
||||
return cached == exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthHeuristicGpu(gpu, divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
||||
{
|
||||
return heuristicCycle == exponent;
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthForExponentCpu(divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
{
|
||||
return cycleLength == exponent;
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisorData) == 1UL;
|
||||
}
|
||||
|
||||
public static bool CycleEqualsExponentHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||
{
|
||||
if (CycleEqualsExponentForMersenneCandidate(divisor, divisorData, exponent))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
var small = Shared._smallCycles;
|
||||
if (small is not null)
|
||||
{
|
||||
ulong cached = small[(int)divisor];
|
||||
if (cached != 0UL)
|
||||
{
|
||||
return cached == exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthHeuristicHybrid(gpu, divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
||||
{
|
||||
return heuristicCycle == exponent;
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthForExponentCpu(divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
{
|
||||
return cycleLength == exponent;
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisorData) == 1UL;
|
||||
}
|
||||
|
||||
public static bool CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||
@@ -111,7 +187,7 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||
return exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisorData) == 1UL;
|
||||
}
|
||||
|
||||
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
||||
@@ -163,7 +239,7 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetCycle(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData)
|
||||
public ulong GetCycle(ulong divisor, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
// Fast-path: in-memory array for small divisors
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
@@ -208,10 +284,106 @@ public class MersenneDivisorCycles
|
||||
// TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
|
||||
// configuration (and skip persisting the result) when the divisor falls outside the in-memory snapshot
|
||||
// so we respect the large-p memory limits and avoid extra cache locks.
|
||||
return CalculateCycleLength(gpu, divisor, divisorData);
|
||||
return CalculateCycleLengthCpu(divisor, divisorData);
|
||||
}
|
||||
|
||||
public static UInt128 GetCycle(PrimeOrderCalculatorAccelerator gpu, UInt128 divisor)
|
||||
public ulong GetCycleGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
// Fast-path: in-memory array for small divisors
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
var arr = _smallCycles;
|
||||
if (arr is not null)
|
||||
{
|
||||
ulong cached = arr[(int)divisor];
|
||||
if (cached != 0UL)
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Binary search for divisor in sorted _table
|
||||
int mid, right, left = 0;
|
||||
ulong d;
|
||||
List<(ulong divisor, ulong cycleLength)> cycles = _table;
|
||||
|
||||
right = cycles.Count - 1;
|
||||
while (left <= right)
|
||||
{
|
||||
mid = left + ((right - left) >> 1);
|
||||
d = cycles[mid].divisor;
|
||||
|
||||
if (d == divisor)
|
||||
{
|
||||
return cycles[mid].cycleLength;
|
||||
}
|
||||
|
||||
if (d < divisor)
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
|
||||
// configuration (and skip persisting the result) when the divisor falls outside the in-memory snapshot
|
||||
// so we respect the large-p memory limits and avoid extra cache locks.
|
||||
return CalculateCycleLengthGpu(gpu, divisor, divisorData);
|
||||
}
|
||||
|
||||
public ulong GetCycleHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
// Fast-path: in-memory array for small divisors
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
var arr = _smallCycles;
|
||||
if (arr is not null)
|
||||
{
|
||||
ulong cached = arr[(int)divisor];
|
||||
if (cached != 0UL)
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Binary search for divisor in sorted _table
|
||||
int mid, right, left = 0;
|
||||
ulong d;
|
||||
List<(ulong divisor, ulong cycleLength)> cycles = _table;
|
||||
|
||||
right = cycles.Count - 1;
|
||||
while (left <= right)
|
||||
{
|
||||
mid = left + ((right - left) >> 1);
|
||||
d = cycles[mid].divisor;
|
||||
|
||||
if (d == divisor)
|
||||
{
|
||||
return cycles[mid].cycleLength;
|
||||
}
|
||||
|
||||
if (d < divisor)
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
|
||||
// configuration (and skip persisting the result) when the divisor falls outside the in-memory snapshot
|
||||
// so we respect the large-p memory limits and avoid extra cache locks.
|
||||
return CalculateCycleLengthHybrid(gpu, divisor, divisorData);
|
||||
}
|
||||
|
||||
public static UInt128 GetCycleCpu(UInt128 divisor)
|
||||
{
|
||||
// For divisor = 2^k, cycle is 1
|
||||
if ((divisor & (divisor - UInt128.One)) == UInt128.Zero)
|
||||
@@ -224,17 +396,111 @@ public class MersenneDivisorCycles
|
||||
ulong divisor64 = (ulong)divisor;
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor64);
|
||||
ulong cycle = CalculateCycleLength(gpu, divisor64, divisorData);
|
||||
ulong cycle = CalculateCycleLengthCpu(divisor64, divisorData);
|
||||
divisorPool.Return(divisorData);
|
||||
return (UInt128)cycle;
|
||||
}
|
||||
|
||||
UInt128 wideOrder = PrimeOrderCalculator.Calculate(
|
||||
UInt128 wideOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (wideOrder != UInt128.Zero)
|
||||
{
|
||||
return wideOrder;
|
||||
}
|
||||
|
||||
// Otherwise, find order of 2 mod divisor
|
||||
// TODO: Port this UInt128 path to the unrolled-hex cycle calculator so wide
|
||||
// divisors stop relying on the slow shift-and-subtract loop measured in the
|
||||
// GPU benchmarks.
|
||||
UInt128 order = UInt128.One;
|
||||
UInt128 pow = UInt128Numbers.Two;
|
||||
while (pow != UInt128.One)
|
||||
{
|
||||
pow <<= 1;
|
||||
if (pow >= divisor)
|
||||
{
|
||||
pow -= divisor;
|
||||
}
|
||||
|
||||
order++;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public static UInt128 GetCycleHybrid(PrimeOrderCalculatorAccelerator gpu, UInt128 divisor)
|
||||
{
|
||||
// For divisor = 2^k, cycle is 1
|
||||
if ((divisor & (divisor - UInt128.One)) == UInt128.Zero)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (divisor <= ulong.MaxValue)
|
||||
{
|
||||
ulong divisor64 = (ulong)divisor;
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor64);
|
||||
ulong cycle = CalculateCycleLengthHybrid(gpu, divisor64, divisorData);
|
||||
divisorPool.Return(divisorData);
|
||||
return (UInt128)cycle;
|
||||
}
|
||||
|
||||
UInt128 wideOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (wideOrder != UInt128.Zero)
|
||||
{
|
||||
return wideOrder;
|
||||
}
|
||||
|
||||
// Otherwise, find order of 2 mod divisor
|
||||
// TODO: Port this UInt128 path to the unrolled-hex cycle calculator so wide
|
||||
// divisors stop relying on the slow shift-and-subtract loop measured in the
|
||||
// GPU benchmarks.
|
||||
UInt128 order = UInt128.One;
|
||||
UInt128 pow = UInt128Numbers.Two;
|
||||
while (pow != UInt128.One)
|
||||
{
|
||||
pow <<= 1;
|
||||
if (pow >= divisor)
|
||||
{
|
||||
pow -= divisor;
|
||||
}
|
||||
|
||||
order++;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public static UInt128 GetCycleGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 divisor)
|
||||
{
|
||||
// For divisor = 2^k, cycle is 1
|
||||
if ((divisor & (divisor - UInt128.One)) == UInt128.Zero)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (divisor <= ulong.MaxValue)
|
||||
{
|
||||
ulong divisor64 = (ulong)divisor;
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor64);
|
||||
ulong cycle = CalculateCycleLengthGpu(gpu, divisor64, divisorData);
|
||||
divisorPool.Return(divisorData);
|
||||
return (UInt128)cycle;
|
||||
}
|
||||
|
||||
UInt128 wideOrder = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (wideOrder != UInt128.Zero)
|
||||
{
|
||||
return wideOrder;
|
||||
@@ -261,7 +527,6 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
|
||||
public static bool TryCalculateCycleLengthForExponentCpu(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong divisor,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
@@ -285,13 +550,11 @@ public class MersenneDivisorCycles
|
||||
// return true;
|
||||
// }
|
||||
|
||||
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
||||
gpu,
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
@@ -331,8 +594,178 @@ public class MersenneDivisorCycles
|
||||
factorCounts[2UL] = twoCount;
|
||||
}
|
||||
|
||||
bool exponentAccumulated = AccumulateFactors(gpu, exponent, factorCounts);
|
||||
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(gpu, k, factorCounts);
|
||||
bool exponentAccumulated = AccumulateFactors(exponent, factorCounts);
|
||||
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCounts);
|
||||
|
||||
if (factorsAccumulated)
|
||||
{
|
||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||
success = true;
|
||||
primeOrderFailed = false;
|
||||
}
|
||||
|
||||
factorCounts.Clear();
|
||||
ThreadStaticPools.ReturnFactorCountDictionary(factorCounts);
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool TryCalculateCycleLengthForExponentHybrid(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong divisor,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed)
|
||||
{
|
||||
cycleLength = 0UL;
|
||||
primeOrderFailed = false;
|
||||
|
||||
// EvenPerfectBitScanner never feeds powers of two into this helper, so leave the branch commented out to avoid redundant work.
|
||||
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
// {
|
||||
// cycleLength = 1UL;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// The scanner only calls this path with prime exponents greater than one and odd divisors above three, keeping the small-parameter guard unreachable.
|
||||
// if (divisor <= 3UL || exponent <= 1UL)
|
||||
// {
|
||||
// cycleLength = CalculateCycleLength(divisor, divisorData);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
|
||||
primeOrderFailed = true;
|
||||
|
||||
ulong phi = divisor - 1UL;
|
||||
// A zero totient would imply divisor == 0, which never occurs on the tested path. Keep the guard documented without
|
||||
// branching in the hot loop.
|
||||
// if (phi == 0UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
int twoCount = BitOperations.TrailingZeroCount(phi);
|
||||
ulong reducedPhi = phi >> twoCount;
|
||||
if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong k = reducedPhi / exponent;
|
||||
// k == 0 would indicate reducedPhi < exponent, which the EvenPerfectBitScanner call flow prevents. Document the
|
||||
// assumption instead of branching.
|
||||
// if (k == 0UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
Dictionary<ulong, int> factorCounts = ThreadStaticPools.RentFactorCountDictionary();
|
||||
bool success = false;
|
||||
|
||||
if (twoCount > 0)
|
||||
{
|
||||
factorCounts[2UL] = twoCount;
|
||||
}
|
||||
|
||||
bool exponentAccumulated = AccumulateFactors(exponent, factorCounts);
|
||||
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCounts);
|
||||
|
||||
if (factorsAccumulated)
|
||||
{
|
||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||
success = true;
|
||||
primeOrderFailed = false;
|
||||
}
|
||||
|
||||
factorCounts.Clear();
|
||||
ThreadStaticPools.ReturnFactorCountDictionary(factorCounts);
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool TryCalculateCycleLengthForExponentGpu(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong divisor,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed)
|
||||
{
|
||||
cycleLength = 0UL;
|
||||
primeOrderFailed = false;
|
||||
|
||||
// EvenPerfectBitScanner never feeds powers of two into this helper, so leave the branch commented out to avoid redundant work.
|
||||
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
// {
|
||||
// cycleLength = 1UL;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// The scanner only calls this path with prime exponents greater than one and odd divisors above three, keeping the small-parameter guard unreachable.
|
||||
// if (divisor <= 3UL || exponent <= 1UL)
|
||||
// {
|
||||
// cycleLength = CalculateCycleLength(divisor, divisorData);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
|
||||
primeOrderFailed = true;
|
||||
|
||||
ulong phi = divisor - 1UL;
|
||||
// A zero totient would imply divisor == 0, which never occurs on the tested path. Keep the guard documented without
|
||||
// branching in the hot loop.
|
||||
// if (phi == 0UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
int twoCount = BitOperations.TrailingZeroCount(phi);
|
||||
ulong reducedPhi = phi >> twoCount;
|
||||
if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong k = reducedPhi / exponent;
|
||||
// k == 0 would indicate reducedPhi < exponent, which the EvenPerfectBitScanner call flow prevents. Document the
|
||||
// assumption instead of branching.
|
||||
// if (k == 0UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
Dictionary<ulong, int> factorCounts = ThreadStaticPools.RentFactorCountDictionary();
|
||||
bool success = false;
|
||||
|
||||
if (twoCount > 0)
|
||||
{
|
||||
factorCounts[2UL] = twoCount;
|
||||
}
|
||||
|
||||
bool exponentAccumulated = AccumulateFactors(exponent, factorCounts);
|
||||
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCounts);
|
||||
|
||||
if (factorsAccumulated)
|
||||
{
|
||||
@@ -347,7 +780,6 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
|
||||
private static bool AccumulateFactors(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong value,
|
||||
Dictionary<ulong, int> counts)
|
||||
{
|
||||
@@ -357,7 +789,7 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
|
||||
Dictionary<ulong, int> scratch = ThreadStaticPools.RentFactorScratchDictionary();
|
||||
bool success = TryFactorIntoCountsInternal(gpu, value, scratch);
|
||||
bool success = TryFactorIntoCountsInternal(value, scratch);
|
||||
if (!success)
|
||||
{
|
||||
scratch.Clear();
|
||||
@@ -379,7 +811,7 @@ public class MersenneDivisorCycles
|
||||
// private static ulong _tryFactorIntoCountsInternalHits;
|
||||
// private static ulong _tryCalculateCycleLengthHeuristicHits;
|
||||
|
||||
private static bool TryFactorIntoCountsInternal(PrimeOrderCalculatorAccelerator gpu, ulong value, Dictionary<ulong, int> counts)
|
||||
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
||||
{
|
||||
// The EvenPerfectBitScanner only requests factors for values >= 2 here. Leave the guard documented without executing it.
|
||||
// if (value <= 1UL)
|
||||
@@ -409,15 +841,7 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
// if (HeuristicPrimeTester.Exclusive.IsPrime(remaining, CancellationToken.None))
|
||||
|
||||
// Atomic.Add(ref _tryFactorIntoCountsInternalHits, 1UL);
|
||||
// Console.WriteLine($"MersenneDivisorCycles.TryFactorIntoCountsInternal hits {Volatile.Read(ref _tryFactorIntoCountsInternalHits)}");
|
||||
|
||||
// HeuristicPrimeTester primeTester = _primeTester ??= new();
|
||||
if (HeuristicPrimeTester.IsPrimeCpu(remaining))
|
||||
// if (HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, remaining))
|
||||
// if (PrimeTester.IsPrime(remaining))
|
||||
{
|
||||
AddFactor(counts, remaining);
|
||||
return true;
|
||||
@@ -441,13 +865,13 @@ public class MersenneDivisorCycles
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (!TryFactorIntoCountsInternal(gpu, factor, counts))
|
||||
if (!TryFactorIntoCountsInternal(factor, counts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Guard the recursive calls to terminate once Pollard-Rho reduces either branch to 1.
|
||||
if (quotient > 1UL && !TryFactorIntoCountsInternal(gpu, quotient, counts))
|
||||
if (quotient > 1UL && !TryFactorIntoCountsInternal(quotient, counts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -765,7 +1189,7 @@ public class MersenneDivisorCycles
|
||||
// unrolled-hex calculator so the CPU generator matches
|
||||
// the GPU benchmark leader for large divisors.
|
||||
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
|
||||
ulong cycle = CalculateCycleLength(gpu, divisor, divisorData);
|
||||
ulong cycle = CalculateCycleLengthGpu(gpu, divisor, divisorData);
|
||||
divisorPool.Return(divisorData);
|
||||
localCycles[localCycleIndex++] = (divisor, cycle);
|
||||
}
|
||||
@@ -933,18 +1357,31 @@ public class MersenneDivisorCycles
|
||||
|
||||
public static ulong CalculateCycleLengthGpu(ulong divisor) => DivisorCycleKernels.CalculateCycleLengthGpu(divisor);
|
||||
|
||||
public static ulong CalculateCycleLength(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
public static ulong CalculateCycleLengthCpu(ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (TryCalculateCycleLengthHeuristic(gpu, divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
|
||||
if (TryCalculateCycleLengthHeuristicCpu(divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
|
||||
return cycleLength;
|
||||
|
||||
return CalculateCycleLengthFallback(divisor);
|
||||
}
|
||||
|
||||
// [ThreadStatic]
|
||||
// private static HeuristicPrimeTester? _primeTester;
|
||||
public static ulong CalculateCycleLengthGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (TryCalculateCycleLengthHeuristicGpu(gpu, divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
|
||||
return cycleLength;
|
||||
|
||||
internal static bool TryCalculateCycleLengthHeuristic(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
return CalculateCycleLengthFallback(divisor);
|
||||
}
|
||||
|
||||
public static ulong CalculateCycleLengthHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (TryCalculateCycleLengthHeuristicHybrid(gpu, divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
|
||||
return cycleLength;
|
||||
|
||||
return CalculateCycleLengthFallback(divisor);
|
||||
}
|
||||
|
||||
internal static bool TryCalculateCycleLengthHeuristicCpu(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
{
|
||||
@@ -958,24 +1395,79 @@ public class MersenneDivisorCycles
|
||||
return true;
|
||||
}
|
||||
|
||||
// if (!skipPrimeOrderHeuristic)
|
||||
// {
|
||||
// Atomic.Add(ref _tryCalculateCycleLengthHeuristicHits, 1UL);
|
||||
// Console.WriteLine($"MersenneDivisorCycles.CalculateCycleLengthFallback hits {Volatile.Read(ref _tryCalculateCycleLengthHeuristicHits)}");
|
||||
// }
|
||||
|
||||
// HeuristicPrimeTester primeTester = _primeTester ??= new();
|
||||
// if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrimeCpu(divisor, CancellationToken.None))
|
||||
if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.IsPrimeCpu(divisor))
|
||||
// if (!skipPrimeOrderHeuristic && HeuristicCombinedPrimeTester.IsPrimeGpu(divisor))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
cycleLength = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryCalculateCycleLengthHeuristicHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
{
|
||||
cycleLength = 1UL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisor <= 3UL)
|
||||
{
|
||||
cycleLength = CalculateCycleLengthFallback(divisor);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.IsPrimeCpu(divisor))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
cycleLength = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryCalculateCycleLengthHeuristicGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
{
|
||||
cycleLength = 1UL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisor <= 3UL)
|
||||
{
|
||||
cycleLength = CalculateCycleLengthFallback(divisor);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.IsPrimeGpu(gpu, divisor))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
|
||||
@@ -25,15 +25,19 @@ public sealed class MersenneNumberTester(
|
||||
bool useGpuScan = true,
|
||||
bool useGpuOrder = true,
|
||||
bool useResidue = true,
|
||||
ulong maxK = 5_000_000UL)
|
||||
ulong maxK = 5_000_000UL,
|
||||
ComputationDevice orderDevice = ComputationDevice.Gpu)
|
||||
{
|
||||
private readonly bool _useResidue = useResidue;
|
||||
private readonly bool _useIncremental = useIncremental && !useResidue;
|
||||
private readonly ulong _maxK = maxK;
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
private readonly bool _useGpuLucas = useGpuLucas;
|
||||
private readonly bool _useGpuScan = useGpuScan; // device for pow2mod/incremental scanning
|
||||
private readonly bool _useGpuOrder = useGpuOrder; // device for order computations
|
||||
private readonly Func<PrimeOrderCalculatorAccelerator, ulong, bool> _sharesFactorWithExponentMinusOne = orderDevice switch
|
||||
{
|
||||
ComputationDevice.Hybrid => static (gpu, exponent) => exponent.SharesFactorWithExponentMinusOneHybrid(gpu),
|
||||
_ when useGpuScan => static (gpu, exponent) => exponent.SharesFactorWithExponentMinusOneGpu(gpu),
|
||||
_ => static (_, exponent) => exponent.SharesFactorWithExponentMinusOneCpu(),
|
||||
};
|
||||
|
||||
private static readonly ConcurrentDictionary<UInt128, ulong> _orderCache = [];
|
||||
// TODO: Swap this ConcurrentDictionary for the pooled dictionary variant highlighted in
|
||||
// Pow2MontgomeryModBenchmarks once order warmups reuse deterministic divisor-cycle snapshots; the pooled approach removed
|
||||
@@ -52,7 +56,154 @@ public sealed class MersenneNumberTester(
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL)
|
||||
public void WarmUpOrdersHybrid(ulong exponent, ulong limit = 5_000_000UL)
|
||||
{
|
||||
bool cacheEnabled = useOrderCache;
|
||||
if (!cacheEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UInt128 twoP = (UInt128)exponent << 1; // 2 * p
|
||||
LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
if (!useGpuOrder)
|
||||
{
|
||||
UInt128 k = UInt128.One;
|
||||
UInt128 q;
|
||||
ulong remainder;
|
||||
for (; k <= limit; k++)
|
||||
{
|
||||
q = twoP * k + UInt128.One;
|
||||
remainder = q.Mod10();
|
||||
bool shouldCheck = lastDigit == LastDigit.Seven
|
||||
? remainder == 7UL || remainder == 9UL || remainder == 3UL
|
||||
: remainder == 1UL || remainder == 3UL || remainder == 9UL;
|
||||
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong mod8 = q.Mod8();
|
||||
if (mod8 != 1UL && mod8 != 7UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (q.Mod3() == 0UL || q.Mod5() == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldCheck || !cacheEnabled || (cacheEnabled && _orderCache.ContainsKey(q)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong ord = q.CalculateOrderHybrid(gpu);
|
||||
_orderCache[q] = ord;
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayPool<UInt128> uInt128Pool = ThreadStaticPools.UInt128Pool;
|
||||
UInt128[] qsBuffer = uInt128Pool.Rent((int)limit);
|
||||
int idx = 0;
|
||||
UInt128 k2 = 1UL;
|
||||
UInt128 q2;
|
||||
ulong remainder2;
|
||||
for (; k2 <= limit; k2++)
|
||||
{
|
||||
q2 = twoP * k2 + 1UL;
|
||||
remainder2 = q2.Mod10();
|
||||
bool shouldCheck2 = lastDigit == LastDigit.Seven
|
||||
? remainder2 == 7UL || remainder2 == 9UL || remainder2 == 3UL
|
||||
: remainder2 == 1UL || remainder2 == 3UL || remainder2 == 9UL;
|
||||
|
||||
if (shouldCheck2)
|
||||
{
|
||||
ulong mod8 = q2.Mod8();
|
||||
if (mod8 != 1UL && mod8 != 7UL)
|
||||
{
|
||||
shouldCheck2 = false;
|
||||
}
|
||||
else if (q2.Mod3() == 0UL || q2.Mod5() == 0UL)
|
||||
{
|
||||
shouldCheck2 = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldCheck2 || (cacheEnabled && _orderCache.ContainsKey(q2)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
qsBuffer[idx++] = q2;
|
||||
}
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
ThreadStaticPools.UInt128Pool.Return(qsBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt128[] qs = qsBuffer;
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
var orderKernel = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.OrderKernelScan).Order!;
|
||||
|
||||
int batchSize = GpuConstants.ScanBatchSize;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
|
||||
int offset = 0;
|
||||
var gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
|
||||
MemoryBuffer1D<GpuUInt128, Stride1D.Dense>? qBuffer;
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer;
|
||||
|
||||
while (offset < idx)
|
||||
{
|
||||
int count = Math.Min(batchSize, idx - offset);
|
||||
qBuffer = accelerator.Allocate1D<GpuUInt128>(count);
|
||||
orderBuffer = accelerator.Allocate1D<ulong>(count);
|
||||
|
||||
var tmp = gpuUInt128Pool.Rent(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
tmp[i] = (GpuUInt128)qs[offset + i];
|
||||
}
|
||||
|
||||
qBuffer.View.CopyFromCPU(stream, ref tmp[0], count);
|
||||
gpuUInt128Pool.Return(tmp);
|
||||
orderKernel(stream, count, exponent, divMul, qBuffer.View, orderBuffer.View);
|
||||
stream.Synchronize();
|
||||
|
||||
ulong[] orders = orderBuffer.GetAsArray1D();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong order = orders[i];
|
||||
if (order == 0UL)
|
||||
{
|
||||
order = qs[offset + i].CalculateOrderHybrid(gpu);
|
||||
}
|
||||
|
||||
if (useOrderCache)
|
||||
{
|
||||
_orderCache[qs[offset + i]] = order;
|
||||
}
|
||||
}
|
||||
|
||||
offset += count;
|
||||
orderBuffer.Dispose();
|
||||
qBuffer.Dispose();
|
||||
}
|
||||
|
||||
AcceleratorStreamPool.Return(acceleratorIndex,stream);
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
ThreadStaticPools.UInt128Pool.Return(qs);
|
||||
}
|
||||
|
||||
public void WarmUpOrdersCpu(ulong exponent, ulong limit = 5_000_000UL)
|
||||
{
|
||||
bool cacheEnabled = useOrderCache;
|
||||
if (!cacheEnabled)
|
||||
@@ -63,8 +214,7 @@ public sealed class MersenneNumberTester(
|
||||
UInt128 twoP = (UInt128)exponent << 1; // 2 * p
|
||||
LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
// CPU path: compute orders directly and cache
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
if (!_useGpuOrder)
|
||||
if (!useGpuOrder)
|
||||
{
|
||||
UInt128 k = UInt128.One;
|
||||
UInt128 q;
|
||||
@@ -98,7 +248,164 @@ public sealed class MersenneNumberTester(
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong ord = q.CalculateOrder(gpu);
|
||||
ulong ord = q.CalculateOrderCpu();
|
||||
_orderCache[q] = ord;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayPool<UInt128> uInt128Pool = ThreadStaticPools.UInt128Pool;
|
||||
UInt128[] qsBuffer = uInt128Pool.Rent((int)limit);
|
||||
int idx = 0;
|
||||
UInt128 k2 = 1UL;
|
||||
UInt128 q2;
|
||||
ulong remainder2;
|
||||
for (; k2 <= limit; k2++)
|
||||
{
|
||||
q2 = twoP * k2 + 1UL;
|
||||
remainder2 = q2.Mod10();
|
||||
// TODO: Reuse the same residue-automaton fast path here so the GPU warmup staging avoids `%` and
|
||||
// branches the benchmarks showed slower than the tracked stepping helper.
|
||||
bool shouldCheck2 = lastDigit == LastDigit.Seven
|
||||
? remainder2 == 7UL || remainder2 == 9UL || remainder2 == 3UL
|
||||
: remainder2 == 1UL || remainder2 == 3UL || remainder2 == 9UL;
|
||||
|
||||
// Early modular filters for odd order and small-prime rejections
|
||||
if (shouldCheck2)
|
||||
{
|
||||
ulong mod8 = q2.Mod8();
|
||||
if (mod8 != 1UL && mod8 != 7UL)
|
||||
{
|
||||
shouldCheck2 = false;
|
||||
}
|
||||
else if (q2.Mod3() == 0UL || q2.Mod5() == 0UL)
|
||||
{
|
||||
shouldCheck2 = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldCheck2 || (cacheEnabled && _orderCache.ContainsKey(q2)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
qsBuffer[idx++] = q2;
|
||||
}
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
ThreadStaticPools.UInt128Pool.Return(qsBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt128[] qs = qsBuffer;
|
||||
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
var orderKernel = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.OrderKernelScan).Order!;
|
||||
|
||||
// Guard long-running kernels by chunking the warm-up across batches.
|
||||
// Reuse the same ScanBatchSize knob used for scanning.
|
||||
int batchSize = GpuConstants.ScanBatchSize;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
|
||||
int offset = 0;
|
||||
var gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
|
||||
MemoryBuffer1D<GpuUInt128, Stride1D.Dense>? qBuffer;
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer;
|
||||
|
||||
while (offset < idx)
|
||||
{
|
||||
int count = Math.Min(batchSize, idx - offset);
|
||||
qBuffer = accelerator.Allocate1D<GpuUInt128>(count);
|
||||
orderBuffer = accelerator.Allocate1D<ulong>(count);
|
||||
|
||||
// Convert to device-friendly GpuUInt128 on the fly
|
||||
var tmp = gpuUInt128Pool.Rent(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
tmp[i] = (GpuUInt128)qs[offset + i];
|
||||
}
|
||||
|
||||
// Copy only the populated elements to the device buffer to avoid overrun on pooled arrays.
|
||||
qBuffer.View.CopyFromCPU(stream, ref tmp[0], count);
|
||||
gpuUInt128Pool.Return(tmp);
|
||||
orderKernel(stream, count, exponent, divMul, qBuffer.View, orderBuffer.View);
|
||||
stream.Synchronize();
|
||||
|
||||
// TODO: Change this to .CopyToCPU and use pre-allocated buffer with fixed, max capacity, using stream. Synchronize on the stream after copy.
|
||||
ulong[] orders = orderBuffer.GetAsArray1D();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong order = orders[i];
|
||||
if (order == 0UL)
|
||||
{
|
||||
order = qs[offset + i].CalculateOrderCpu();
|
||||
}
|
||||
|
||||
if (useOrderCache)
|
||||
{
|
||||
_orderCache[qs[offset + i]] = order;
|
||||
}
|
||||
}
|
||||
|
||||
offset += count;
|
||||
orderBuffer.Dispose();
|
||||
qBuffer.Dispose();
|
||||
}
|
||||
|
||||
AcceleratorStreamPool.Return(acceleratorIndex,stream);
|
||||
uInt128Pool.Return(qs);
|
||||
}
|
||||
|
||||
public void WarmUpOrdersGpu(ulong exponent, ulong limit = 5_000_000UL)
|
||||
{
|
||||
bool cacheEnabled = useOrderCache;
|
||||
if (!cacheEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UInt128 twoP = (UInt128)exponent << 1; // 2 * p
|
||||
LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
// CPU path: compute orders directly and cache
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
if (!useGpuOrder)
|
||||
{
|
||||
UInt128 k = UInt128.One;
|
||||
UInt128 q;
|
||||
ulong remainder;
|
||||
for (; k <= limit; k++)
|
||||
{
|
||||
q = twoP * k + UInt128.One;
|
||||
remainder = q.Mod10();
|
||||
// TODO: Replace these repeated Mod10/Mod8/Mod3/Mod5 calls with a residue automaton walk so
|
||||
// order warmups reuse the optimized cycle stepping validated in the residue benchmarks.
|
||||
bool shouldCheck = lastDigit == LastDigit.Seven
|
||||
? remainder == 7UL || remainder == 9UL || remainder == 3UL
|
||||
: remainder == 1UL || remainder == 3UL || remainder == 9UL;
|
||||
|
||||
// Early modular filters for odd order and small-prime rejections
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong mod8 = q.Mod8();
|
||||
if (mod8 != 1UL && mod8 != 7UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (q.Mod3() == 0UL || q.Mod5() == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldCheck || !cacheEnabled || (cacheEnabled && _orderCache.ContainsKey(q)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong ord = q.CalculateOrderGpu(gpu);
|
||||
_orderCache[q] = ord;
|
||||
}
|
||||
|
||||
@@ -194,7 +501,7 @@ public sealed class MersenneNumberTester(
|
||||
ulong order = orders[i];
|
||||
if (order == 0UL)
|
||||
{
|
||||
order = qs[offset + i].CalculateOrder(gpu);
|
||||
order = qs[offset + i].CalculateOrderGpu(gpu);
|
||||
}
|
||||
|
||||
if (useOrderCache)
|
||||
@@ -225,7 +532,7 @@ public sealed class MersenneNumberTester(
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu))
|
||||
if ((exponent & 3UL) == 1UL && _sharesFactorWithExponentMinusOne(gpu, exponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -250,13 +557,13 @@ public sealed class MersenneNumberTester(
|
||||
maxK = _maxK;
|
||||
}
|
||||
|
||||
if (_useGpuScan)
|
||||
if (useGpuScan)
|
||||
{
|
||||
_residueGpuTester!.Scan(exponent, twoP, lastDigit, maxK, ref prePrime);
|
||||
_residueGpuTester!.Scan(gpu, exponent, twoP, lastDigit, maxK, ref prePrime);
|
||||
}
|
||||
else
|
||||
{
|
||||
_residueCpuTester!.Scan(gpu, exponent, twoP, lastDigit, maxK, ref prePrime);
|
||||
_residueCpuTester!.Scan(exponent, twoP, lastDigit, maxK, ref prePrime);
|
||||
}
|
||||
|
||||
return prePrime;
|
||||
@@ -269,7 +576,7 @@ public sealed class MersenneNumberTester(
|
||||
UInt128 twoPPre = (UInt128)exponent << 1;
|
||||
UInt128 maxKPre = (UInt128)PreLucasPreScanK;
|
||||
LastDigit lastDigitPre = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
if (_useGpuScan)
|
||||
if (useGpuScan)
|
||||
{
|
||||
_incrementalGpuTester.Scan(exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime);
|
||||
}
|
||||
@@ -283,7 +590,7 @@ public sealed class MersenneNumberTester(
|
||||
return false;
|
||||
}
|
||||
|
||||
return _useGpuLucas
|
||||
return useGpuLucas
|
||||
? _lucasLehmerGpuTester.IsPrime(gpu, exponent, runOnGpu: true)
|
||||
: _lucasLehmerCpuTester.IsPrime(gpu, exponent);
|
||||
}
|
||||
@@ -292,7 +599,7 @@ public sealed class MersenneNumberTester(
|
||||
|
||||
if (useOrder)
|
||||
{
|
||||
if (_useGpuOrder)
|
||||
if (useGpuOrder)
|
||||
{
|
||||
_orderGpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime);
|
||||
}
|
||||
@@ -303,7 +610,7 @@ public sealed class MersenneNumberTester(
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_useGpuScan)
|
||||
if (useGpuScan)
|
||||
{
|
||||
_incrementalGpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime);
|
||||
}
|
||||
@@ -332,6 +639,4 @@ public sealed class MersenneNumberTester(
|
||||
UInt128 pow = exponent.PowMod(q);
|
||||
orders[index] = pow == 1UL ? exponent : 0UL;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -39,12 +39,9 @@ public sealed class ModResidueTracker
|
||||
/// (number must be non-decreasing across calls). Adds and initializes new
|
||||
/// divisors on first use.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDivisible(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
public bool IsDivisibleCpu(UInt128 number, UInt128 divisor)
|
||||
{
|
||||
if (divisor == UInt128.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(divisor));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(divisor, UInt128.Zero);
|
||||
|
||||
EnsureAt(number);
|
||||
|
||||
@@ -52,7 +49,75 @@ public sealed class ModResidueTracker
|
||||
int idx = LowerBound(divisors, divisor);
|
||||
if (idx == divisors.Count || divisors[idx] != divisor)
|
||||
{
|
||||
UInt128 residue = ComputeInitialResidue(gpu, number, divisor);
|
||||
UInt128 residue = ComputeInitialResidueCpu(number, divisor);
|
||||
divisors.Insert(idx, divisor);
|
||||
_residues.Insert(idx, residue);
|
||||
if (_hasLastAppended && idx == divisors.Count - 1 && divisor >= _lastAppended)
|
||||
{
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
else if (!_hasLastAppended)
|
||||
{
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
|
||||
return residue == UInt128.Zero;
|
||||
}
|
||||
|
||||
return _residues[idx] == UInt128.Zero;
|
||||
}
|
||||
|
||||
/// Returns true if the provided number (either p or M_p depending on model)
|
||||
/// is divisible by the divisor. Advances internal state forward if needed
|
||||
/// (number must be non-decreasing across calls). Adds and initializes new
|
||||
/// divisors on first use.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDivisibleGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(divisor, UInt128.Zero);
|
||||
|
||||
EnsureAt(number);
|
||||
|
||||
List<UInt128> divisors = _divisors;
|
||||
int idx = LowerBound(divisors, divisor);
|
||||
if (idx == divisors.Count || divisors[idx] != divisor)
|
||||
{
|
||||
UInt128 residue = ComputeInitialResidueGpu(gpu, number, divisor);
|
||||
divisors.Insert(idx, divisor);
|
||||
_residues.Insert(idx, residue);
|
||||
if (_hasLastAppended && idx == divisors.Count - 1 && divisor >= _lastAppended)
|
||||
{
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
else if (!_hasLastAppended)
|
||||
{
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
|
||||
return residue == UInt128.Zero;
|
||||
}
|
||||
|
||||
return _residues[idx] == UInt128.Zero;
|
||||
}
|
||||
|
||||
/// Returns true if the provided number (either p or M_p depending on model)
|
||||
/// is divisible by the divisor. Advances internal state forward if needed
|
||||
/// (number must be non-decreasing across calls). Adds and initializes new
|
||||
/// divisors on first use.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDivisibleHybrid(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(divisor, UInt128.Zero);
|
||||
|
||||
EnsureAt(number);
|
||||
|
||||
List<UInt128> divisors = _divisors;
|
||||
int idx = LowerBound(divisors, divisor);
|
||||
if (idx == divisors.Count || divisors[idx] != divisor)
|
||||
{
|
||||
UInt128 residue = ComputeInitialResidueHybrid(gpu, number, divisor);
|
||||
divisors.Insert(idx, divisor);
|
||||
_residues.Insert(idx, residue);
|
||||
if (_hasLastAppended && idx == divisors.Count - 1 && divisor >= _lastAppended)
|
||||
@@ -81,7 +146,7 @@ public sealed class ModResidueTracker
|
||||
// Merge or append current candidate divisor (ascending order required across calls).
|
||||
// Returns true and sets 'divisible' based on residue at 'number'.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MergeOrAppend(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor, out bool divisible)
|
||||
public bool MergeOrAppendCpu(UInt128 number, UInt128 divisor, out bool divisible)
|
||||
{
|
||||
// Assume number is non-decreasing; advance residues lazily when number changes.
|
||||
List<UInt128> divisors = _divisors;
|
||||
@@ -97,7 +162,7 @@ public sealed class ModResidueTracker
|
||||
List<UInt128> residues = _residues;
|
||||
if (divisors.Count == 0 || (_hasLastAppended && divisor >= _lastAppended && divisors[^1] <= divisor))
|
||||
{
|
||||
residue = ComputeInitialResidue(gpu, number, divisor);
|
||||
residue = ComputeInitialResidueCpu(number, divisor);
|
||||
divisors.Add(divisor);
|
||||
residues.Add(residue);
|
||||
_hasLastAppended = true;
|
||||
@@ -123,7 +188,129 @@ public sealed class ModResidueTracker
|
||||
|
||||
|
||||
// Insert at cursor to keep order
|
||||
residue = ComputeInitialResidue(gpu, number, divisor);
|
||||
residue = ComputeInitialResidueCpu(number, divisor);
|
||||
divisors.Insert(cursor, divisor);
|
||||
residues.Insert(cursor, residue);
|
||||
divisible = residue == UInt128.Zero;
|
||||
_cursor = cursor + 1;
|
||||
|
||||
// update last appended hint
|
||||
if (!_hasLastAppended || divisor >= _lastAppended)
|
||||
{
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merge or append current candidate divisor (ascending order required across calls).
|
||||
// Returns true and sets 'divisible' based on residue at 'number'.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MergeOrAppendGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor, out bool divisible)
|
||||
{
|
||||
// Assume number is non-decreasing; advance residues lazily when number changes.
|
||||
List<UInt128> divisors = _divisors;
|
||||
if (number != _currentNumber)
|
||||
{
|
||||
EnsureAt(number);
|
||||
// After EnsureAt we can keep _cursor (still valid monotonic pointer)
|
||||
if (_cursor > divisors.Count) _cursor = divisors.Count;
|
||||
}
|
||||
|
||||
UInt128 residue;
|
||||
// Fast path: append to tail when candidates are strictly increasing.
|
||||
List<UInt128> residues = _residues;
|
||||
if (divisors.Count == 0 || (_hasLastAppended && divisor >= _lastAppended && divisors[^1] <= divisor))
|
||||
{
|
||||
residue = ComputeInitialResidueGpu(gpu, number, divisor);
|
||||
divisors.Add(divisor);
|
||||
residues.Add(residue);
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
_cursor = divisors.Count; // position after last
|
||||
divisible = residue == UInt128.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merge pointer: advance until current >= divisor
|
||||
var cursor = _cursor;
|
||||
while (cursor < divisors.Count && divisors[cursor] < divisor)
|
||||
{
|
||||
cursor++;
|
||||
}
|
||||
|
||||
if (cursor < divisors.Count && divisors[cursor] == divisor)
|
||||
{
|
||||
divisible = residues[cursor] == UInt128.Zero;
|
||||
_cursor = cursor + 1; // position after matched
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Insert at cursor to keep order
|
||||
residue = ComputeInitialResidueGpu(gpu, number, divisor);
|
||||
divisors.Insert(cursor, divisor);
|
||||
residues.Insert(cursor, residue);
|
||||
divisible = residue == UInt128.Zero;
|
||||
_cursor = cursor + 1;
|
||||
|
||||
// update last appended hint
|
||||
if (!_hasLastAppended || divisor >= _lastAppended)
|
||||
{
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merge or append current candidate divisor (ascending order required across calls).
|
||||
// Returns true and sets 'divisible' based on residue at 'number'.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MergeOrAppendHybrid(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor, out bool divisible)
|
||||
{
|
||||
// Assume number is non-decreasing; advance residues lazily when number changes.
|
||||
List<UInt128> divisors = _divisors;
|
||||
if (number != _currentNumber)
|
||||
{
|
||||
EnsureAt(number);
|
||||
// After EnsureAt we can keep _cursor (still valid monotonic pointer)
|
||||
if (_cursor > divisors.Count) _cursor = divisors.Count;
|
||||
}
|
||||
|
||||
UInt128 residue;
|
||||
// Fast path: append to tail when candidates are strictly increasing.
|
||||
List<UInt128> residues = _residues;
|
||||
if (divisors.Count == 0 || (_hasLastAppended && divisor >= _lastAppended && divisors[^1] <= divisor))
|
||||
{
|
||||
residue = ComputeInitialResidueHybrid(gpu, number, divisor);
|
||||
divisors.Add(divisor);
|
||||
residues.Add(residue);
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
_cursor = divisors.Count; // position after last
|
||||
divisible = residue == UInt128.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merge pointer: advance until current >= divisor
|
||||
var cursor = _cursor;
|
||||
while (cursor < divisors.Count && divisors[cursor] < divisor)
|
||||
{
|
||||
cursor++;
|
||||
}
|
||||
|
||||
if (cursor < divisors.Count && divisors[cursor] == divisor)
|
||||
{
|
||||
divisible = residues[cursor] == UInt128.Zero;
|
||||
_cursor = cursor + 1; // position after matched
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Insert at cursor to keep order
|
||||
residue = ComputeInitialResidueHybrid(gpu, number, divisor);
|
||||
divisors.Insert(cursor, divisor);
|
||||
residues.Insert(cursor, residue);
|
||||
divisible = residue == UInt128.Zero;
|
||||
@@ -238,7 +425,7 @@ public sealed class ModResidueTracker
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private UInt128 ComputeInitialResidue(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
private UInt128 ComputeInitialResidueCpu(UInt128 number, UInt128 divisor)
|
||||
{
|
||||
if (_model == ResidueModel.Identity)
|
||||
{
|
||||
@@ -266,12 +453,82 @@ public sealed class ModResidueTracker
|
||||
if (pow != UInt128.One)
|
||||
return (pow - UInt128.One) % divisor;
|
||||
|
||||
ulong ord = divisor.CalculateOrder(gpu);
|
||||
ulong ord = divisor.CalculateOrderCpu();
|
||||
if (ord == 0UL || number % (UInt128)ord != UInt128.Zero)
|
||||
return UInt128.One; // does NOT divide
|
||||
|
||||
return UInt128.Zero; // q really divides M_p
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private UInt128 ComputeInitialResidueGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
{
|
||||
if (_model == ResidueModel.Identity)
|
||||
{
|
||||
// Fast path for 64-bit inputs
|
||||
if (number <= ulong.MaxValue && divisor <= ulong.MaxValue)
|
||||
{
|
||||
// TODO: Swap this modulo fallback for the shared divisor-cycle remainder helper so 64-bit
|
||||
// residues reuse cached cycles instead of recomputing `%` on every lookup.
|
||||
return (ulong)number % (ulong)divisor;
|
||||
}
|
||||
|
||||
// TODO: Use the Montgomery folding helper measured faster in the Pow2Montgomery benchmarks so this
|
||||
// wide residue path avoids general `%` on UInt128 inputs.
|
||||
return number % divisor;
|
||||
}
|
||||
|
||||
// residue of Mersenne number M_number = 2^number - 1 modulo divisor
|
||||
// TODO: Consult MersenneDivisorCycles.Shared (or similar caches) here so we reuse precomputed cycle lengths
|
||||
// instead of recomputing powmods for every divisor; the divisor-cycle benchmarks showed large wins once the
|
||||
// cached orders were used across scans, and a miss should trigger the configured device to compute the cycle
|
||||
// immediately without storing it back or requesting additional cache blocks.
|
||||
UInt128 pow = number.Pow2MontgomeryModWindowed(divisor);
|
||||
|
||||
// q divides M_p ⇔ 2^p ≡ 1 (mod q) AND ord_q(2) | p
|
||||
if (pow != UInt128.One)
|
||||
return (pow - UInt128.One) % divisor;
|
||||
|
||||
ulong ord = divisor.CalculateOrderGpu(gpu);
|
||||
if (ord == 0UL || number % (UInt128)ord != UInt128.Zero)
|
||||
return UInt128.One; // does NOT divide
|
||||
|
||||
return UInt128.Zero; // q really divides M_p
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private UInt128 ComputeInitialResidueHybrid(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
{
|
||||
if (_model == ResidueModel.Identity)
|
||||
{
|
||||
// Fast path for 64-bit inputs
|
||||
if (number <= ulong.MaxValue && divisor <= ulong.MaxValue)
|
||||
{
|
||||
// TODO: Swap this modulo fallback for the shared divisor-cycle remainder helper so 64-bit
|
||||
// residues reuse cached cycles instead of recomputing `%` on every lookup.
|
||||
return (ulong)number % (ulong)divisor;
|
||||
}
|
||||
|
||||
// TODO: Use the Montgomery folding helper measured faster in the Pow2Montgomery benchmarks so this
|
||||
// wide residue path avoids general `%` on UInt128 inputs.
|
||||
return number % divisor;
|
||||
}
|
||||
|
||||
// residue of Mersenne number M_number = 2^number - 1 modulo divisor
|
||||
// TODO: Consult MersenneDivisorCycles.Shared (or similar caches) here so we reuse precomputed cycle lengths
|
||||
// instead of recomputing powmods for every divisor; the divisor-cycle benchmarks showed large wins once the
|
||||
// cached orders were used across scans, and a miss should trigger the configured device to compute the cycle
|
||||
// immediately without storing it back or requesting additional cache blocks.
|
||||
UInt128 pow = number.Pow2MontgomeryModWindowed(divisor);
|
||||
|
||||
// q divides M_p ⇔ 2^p ≡ 1 (mod q) AND ord_q(2) | p
|
||||
if (pow != UInt128.One)
|
||||
return (pow - UInt128.One) % divisor;
|
||||
|
||||
ulong ord = divisor.CalculateOrderHybrid(gpu);
|
||||
if (ord == 0UL || number % (UInt128)ord != UInt128.Zero)
|
||||
return UInt128.One; // does NOT divide
|
||||
|
||||
return UInt128.Zero; // q really divides M_p
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,4 @@
|
||||
using System.Buffers;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
@@ -7,17 +8,408 @@ namespace PerfectNumbers.Core;
|
||||
|
||||
internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2;
|
||||
|
||||
private const int GpuSmallPrimeFactorSlots = 64;
|
||||
|
||||
public static ulong CalculateGpu(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong prime,
|
||||
ulong? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
in PrimeOrderSearchConfig config)
|
||||
{
|
||||
// TODO: Is this condition ever met on EvenPerfectBitScanner's execution path? If not, we can add a clarification comment and comment out the entire block. We want to support p candidates at least greater or equal to 31.
|
||||
if (prime <= 3UL)
|
||||
{
|
||||
return prime == 3UL ? 2UL : 1UL;
|
||||
}
|
||||
|
||||
ulong phi = prime - 1UL;
|
||||
|
||||
if (PrimeOrderGpuHeuristics.TryCalculateOrder(gpu, prime, previousOrder, config, divisorData, out ulong gpuOrder))
|
||||
{
|
||||
return gpuOrder;
|
||||
}
|
||||
|
||||
PartialFactorResult phiFactors = PartialFactorGpu(gpu, phi, config);
|
||||
|
||||
ulong result;
|
||||
if (phiFactors.Factors is null)
|
||||
{
|
||||
result = CalculateByFactorizationCpu(prime, divisorData);
|
||||
|
||||
phiFactors.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
result = RunHeuristicPipelineCpu(prime, previousOrder, config, divisorData, phi, phiFactors);
|
||||
phiFactors.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static UInt128 CalculateGpu(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in UInt128 prime,
|
||||
in UInt128? previousOrder,
|
||||
in PrimeOrderSearchConfig config)
|
||||
{
|
||||
MontgomeryDivisorData divisorData;
|
||||
UInt128 result;
|
||||
if (prime <= ulong.MaxValue)
|
||||
{
|
||||
ulong? previous = null;
|
||||
if (previousOrder.HasValue)
|
||||
{
|
||||
UInt128 previousValue = previousOrder.Value;
|
||||
if (previousValue <= ulong.MaxValue)
|
||||
{
|
||||
previous = (ulong)previousValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
previous = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
ulong prime64 = (ulong)prime;
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
divisorData = divisorPool.FromModulus(prime64);
|
||||
ulong order64 = CalculateGpu(gpu, prime64, previous, divisorData, config);
|
||||
divisorPool.Return(divisorData);
|
||||
result = order64 == 0UL ? UInt128.Zero : (UInt128)order64;
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorData = MontgomeryDivisorData.Empty;
|
||||
result = CalculateWideInternalGpu(gpu, prime, previousOrder, divisorData, config);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static UInt128 CalculateWideInternalGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (prime <= UInt128.One)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (prime == (UInt128)3UL)
|
||||
{
|
||||
return (UInt128)2UL;
|
||||
}
|
||||
|
||||
UInt128 phi = prime - UInt128.One;
|
||||
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
||||
UInt128 result;
|
||||
if (phiFactors.Factors is null)
|
||||
{
|
||||
result = FinishStrictlyWideGpu(gpu, prime, divisorData);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = RunHeuristicPipelineWideGpu(gpu, prime, previousOrder, divisorData, config, phi, phiFactors);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static UInt128 RunHeuristicPipelineWideGpu(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in UInt128 prime,
|
||||
in UInt128? previousOrder,
|
||||
in MontgomeryDivisorData? divisorData,
|
||||
in PrimeOrderSearchConfig config,
|
||||
in UInt128 phi,
|
||||
in PartialFactorResult128 phiFactors)
|
||||
{
|
||||
if (phiFactors.FullyFactored && TrySpecialMaxWideGpu(gpu, phi, prime, divisorData, phiFactors))
|
||||
{
|
||||
return phi;
|
||||
}
|
||||
|
||||
UInt128 candidateOrder = InitializeStartingOrderWideGpu(gpu, prime, phi, divisorData);
|
||||
candidateOrder = ExponentLoweringWideGpu(gpu, candidateOrder, prime, divisorData, phiFactors);
|
||||
|
||||
if (TryConfirmOrderWideGpu(gpu, prime, candidateOrder, divisorData, config))
|
||||
{
|
||||
return candidateOrder;
|
||||
}
|
||||
|
||||
if (config.Mode == PrimeOrderMode.Strict)
|
||||
{
|
||||
return FinishStrictlyWideGpu(gpu, prime, divisorData);
|
||||
}
|
||||
|
||||
if (TryHeuristicFinishWideGpu(gpu, prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
return candidateOrder;
|
||||
}
|
||||
|
||||
private static UInt128 FinishStrictlyWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in MontgomeryDivisorData? divisorData)
|
||||
{
|
||||
UInt128 phi = prime - UInt128.One;
|
||||
Dictionary<UInt128, int> counts = new(capacity: 8);
|
||||
FactorCompletelyWide(phi, counts);
|
||||
if (counts.Count == 0)
|
||||
{
|
||||
return phi;
|
||||
}
|
||||
|
||||
List<KeyValuePair<UInt128, int>> entries = new(counts);
|
||||
entries.Sort(static (a, b) => a.Key.CompareTo(b.Key));
|
||||
|
||||
UInt128 order = phi;
|
||||
int entryCount = entries.Count;
|
||||
for (int i = 0; i < entryCount; i++)
|
||||
{
|
||||
UInt128 primeFactor = entries[i].Key;
|
||||
int exponent = entries[i].Value;
|
||||
for (int iteration = 0; iteration < exponent; iteration++)
|
||||
{
|
||||
if ((order % primeFactor) != UInt128.Zero)
|
||||
{
|
||||
UInt128 candidate = order / primeFactor;
|
||||
if (Pow2ModWideGpu(gpu, candidate, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = candidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
private static bool TrySpecialMaxWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData? divisorData, in PartialFactorResult128 factors)
|
||||
{
|
||||
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
||||
int length = factors.Count;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
UInt128 factor = factorSpan[i].Value;
|
||||
UInt128 reduced = phi / factor;
|
||||
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UInt128 InitializeStartingOrderWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData? divisorData)
|
||||
{
|
||||
UInt128 order = phi;
|
||||
UInt128 mod8 = prime & (UInt128)7UL;
|
||||
if (mod8 == UInt128.One || mod8 == (UInt128)7UL)
|
||||
{
|
||||
UInt128 half = phi >> 1;
|
||||
if (Pow2ModWideGpu(gpu, half, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = half;
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
private static UInt128 ExponentLoweringWideGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 order, in UInt128 prime, in MontgomeryDivisorData? divisorData, in PartialFactorResult128 factors)
|
||||
{
|
||||
ArrayPool<FactorEntry128> pool = ThreadStaticPools.FactorEntry128Pool;
|
||||
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
||||
int length = factors.Count;
|
||||
// TODO: This should never trigger from production code - check
|
||||
// if (length == 0)
|
||||
// {
|
||||
// return order;
|
||||
// }
|
||||
FactorEntry128[] tempArray = pool.Rent(length + 1);
|
||||
|
||||
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
|
||||
factorSpan.CopyTo(buffer);
|
||||
|
||||
if (!factors.FullyFactored && factors.Cofactor > UInt128.One && IsPrimeWide(factors.Cofactor))
|
||||
{
|
||||
buffer[length] = new FactorEntry128(factors.Cofactor, 1);
|
||||
length++;
|
||||
}
|
||||
|
||||
buffer[..length].Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||
|
||||
// TODO: Investigating using stepper to replace modulo operation
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
UInt128 primeFactor = buffer[i].Value;
|
||||
int exponent = buffer[i].Exponent;
|
||||
for (int iteration = 0; iteration < exponent; iteration++)
|
||||
{
|
||||
if ((order % primeFactor) == UInt128.Zero)
|
||||
{
|
||||
UInt128 reduced = order / primeFactor;
|
||||
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = reduced;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pool.Return(tempArray, clearArray: false);
|
||||
return order;
|
||||
}
|
||||
|
||||
private static bool TryConfirmOrderWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 order, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (order == UInt128.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Pow2ModWideGpu(gpu, order, prime, divisorData) != UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PartialFactorResult128 factorization = PartialFactorWide(order, config);
|
||||
if (factorization.Factors is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!factorization.FullyFactored)
|
||||
{
|
||||
if (factorization.Cofactor <= UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPrimeWide(factorization.Cofactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
|
||||
}
|
||||
|
||||
ReadOnlySpan<FactorEntry128> span = factorization.Factors;
|
||||
int length = factorization.Count;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
UInt128 primeFactor = span[i].Value;
|
||||
UInt128 reduced = order;
|
||||
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
|
||||
{
|
||||
if ((reduced % primeFactor) != UInt128.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
reduced /= primeFactor;
|
||||
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryHeuristicFinishWideGpu(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in UInt128 prime,
|
||||
in UInt128 order,
|
||||
in UInt128? previousOrder,
|
||||
in MontgomeryDivisorData? divisorData,
|
||||
in PrimeOrderSearchConfig config,
|
||||
out UInt128 result)
|
||||
{
|
||||
result = UInt128.Zero;
|
||||
if (order <= UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PartialFactorResult128 orderFactors = PartialFactorWide(order, config);
|
||||
if (orderFactors.Factors is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!orderFactors.FullyFactored)
|
||||
{
|
||||
if (orderFactors.Cofactor <= UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPrimeWide(orderFactors.Cofactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
orderFactors = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
||||
}
|
||||
|
||||
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
|
||||
List<UInt128> candidates = new(capacity);
|
||||
FactorEntry128[] factorArray = orderFactors.Factors!;
|
||||
BuildCandidatesWide(order, factorArray, orderFactors.Count, candidates, capacity);
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SortCandidatesWide(prime, previousOrder, candidates);
|
||||
|
||||
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
|
||||
int powUsed = 0;
|
||||
int candidateCount = candidates.Count;
|
||||
|
||||
for (int i = 0; i < candidateCount; i++)
|
||||
{
|
||||
if (powUsed >= powBudget && powBudget > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
UInt128 candidate = candidates[i];
|
||||
powUsed++;
|
||||
|
||||
if (Pow2ModWideGpu(gpu, candidate, prime, divisorData) != UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryConfirmCandidateWideGpu(gpu, prime, candidate, config, ref powUsed, powBudget, divisorData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ulong CalculateByFactorizationGpu(PrimeOrderCalculatorAccelerator gpu, ulong prime, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
ulong phi = prime - 1UL;
|
||||
Dictionary<ulong, int> counts = gpu.Pow2ModEntriesToTestOnHost;
|
||||
counts.Clear();
|
||||
|
||||
FactorCompletelyCpu(gpu, phi, counts);
|
||||
FactorCompletelyCpu(phi, counts, false);
|
||||
if (counts.Count == 0)
|
||||
{
|
||||
return phi;
|
||||
@@ -156,6 +548,86 @@ internal static partial class PrimeOrderCalculator
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
private static UInt128 Pow2ModWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData? divisorData)
|
||||
{
|
||||
if (modulus == UInt128.One)
|
||||
{
|
||||
return UInt128.Zero;
|
||||
}
|
||||
|
||||
if (modulus <= (UInt128)ulong.MaxValue && exponent <= (UInt128)ulong.MaxValue)
|
||||
{
|
||||
ulong prime64 = (ulong)modulus;
|
||||
ulong exponent64 = (ulong)exponent;
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent64, prime64, out ulong remainder, divisorData);
|
||||
if (status == GpuPow2ModStatus.Success)
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
|
||||
GpuPow2ModStatus wideStatus = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, modulus, out UInt128 wideRemainder);
|
||||
if (wideStatus == GpuPow2ModStatus.Success)
|
||||
{
|
||||
return wideRemainder;
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowed(modulus);
|
||||
}
|
||||
|
||||
private static bool TryConfirmCandidateWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData? divisorData)
|
||||
{
|
||||
PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
|
||||
if (factorization.Factors is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!factorization.FullyFactored)
|
||||
{
|
||||
if (factorization.Cofactor <= UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPrimeWide(factorization.Cofactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
|
||||
}
|
||||
|
||||
ReadOnlySpan<FactorEntry128> span = factorization.Factors;
|
||||
int length = factorization.Count;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
UInt128 primeFactor = span[i].Value;
|
||||
UInt128 reduced = candidate;
|
||||
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
|
||||
{
|
||||
if ((reduced % primeFactor) != UInt128.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
reduced /= primeFactor;
|
||||
if (powUsed >= powBudget && powBudget > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
powUsed++;
|
||||
if (Pow2ModWideGpu(gpu, reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryPollardRhoGpu(PrimeOrderCalculatorAccelerator gpu, ulong n, out ulong factor)
|
||||
{
|
||||
Span<ulong> randomStateSpan = stackalloc ulong[1];
|
||||
@@ -184,6 +656,5 @@ internal static partial class PrimeOrderCalculator
|
||||
factor = factored ? factorSpan[0] : 0UL;
|
||||
return factored;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
public static ulong CalculateHybrid(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong prime,
|
||||
ulong? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
in PrimeOrderSearchConfig config)
|
||||
{
|
||||
// TODO: Is this condition ever met on EvenPerfectBitScanner's execution path? If not, we can add a clarification comment and comment out the entire block. We want to support p candidates at least greater or equal to 31.
|
||||
if (prime <= 3UL)
|
||||
{
|
||||
return prime == 3UL ? 2UL : 1UL;
|
||||
}
|
||||
|
||||
ulong phi = prime - 1UL;
|
||||
|
||||
PartialFactorResult phiFactors = PartialFactorHybrid(gpu, phi, config);
|
||||
|
||||
ulong result;
|
||||
if (phiFactors.Factors is null)
|
||||
{
|
||||
result = CalculateByFactorizationCpu(prime, divisorData);
|
||||
|
||||
phiFactors.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
result = RunHeuristicPipelineHybrid(gpu, prime, previousOrder, config, divisorData, phi, phiFactors);
|
||||
|
||||
phiFactors.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static UInt128 CalculateHybrid(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in UInt128 prime,
|
||||
in UInt128? previousOrder,
|
||||
in PrimeOrderSearchConfig config)
|
||||
{
|
||||
MontgomeryDivisorData divisorData;
|
||||
UInt128 result;
|
||||
if (prime <= ulong.MaxValue)
|
||||
{
|
||||
ulong? previous = null;
|
||||
if (previousOrder.HasValue)
|
||||
{
|
||||
UInt128 previousValue = previousOrder.Value;
|
||||
if (previousValue <= ulong.MaxValue)
|
||||
{
|
||||
previous = (ulong)previousValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
previous = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
ulong prime64 = (ulong)prime;
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
divisorData = divisorPool.FromModulus(prime64);
|
||||
ulong order64 = CalculateHybrid(gpu, prime64, previous, divisorData, config);
|
||||
divisorPool.Return(divisorData);
|
||||
result = order64 == 0UL ? UInt128.Zero : (UInt128)order64;
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorData = MontgomeryDivisorData.Empty;
|
||||
result = CalculateWideInternalCpu(prime, previousOrder, divisorData, config);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -8,365 +8,6 @@ namespace PerfectNumbers.Core;
|
||||
|
||||
internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static bool s_pow2ModeInitialized;
|
||||
|
||||
[ThreadStatic]
|
||||
private static bool s_allowGpuPow2;
|
||||
|
||||
[ThreadStatic]
|
||||
private static PrimeOrderHeuristicDevice s_deviceMode;
|
||||
|
||||
[ThreadStatic]
|
||||
private static bool s_debugLoggingEnabled;
|
||||
|
||||
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
|
||||
|
||||
private readonly struct Pow2ModeScope
|
||||
{
|
||||
private readonly bool _previousInitialized;
|
||||
private readonly bool _previousAllow;
|
||||
private readonly PrimeOrderHeuristicDevice _previousDevice;
|
||||
|
||||
public Pow2ModeScope(PrimeOrderHeuristicDevice device)
|
||||
{
|
||||
_previousInitialized = s_pow2ModeInitialized;
|
||||
_previousAllow = s_allowGpuPow2;
|
||||
_previousDevice = s_deviceMode;
|
||||
s_pow2ModeInitialized = true;
|
||||
s_allowGpuPow2 = device == PrimeOrderHeuristicDevice.Gpu;
|
||||
s_deviceMode = device;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s_allowGpuPow2 = _previousAllow;
|
||||
s_pow2ModeInitialized = _previousInitialized;
|
||||
s_deviceMode = _previousDevice;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct DebugLoggingScope
|
||||
{
|
||||
private readonly bool _previous;
|
||||
|
||||
public DebugLoggingScope(bool enabled)
|
||||
{
|
||||
_previous = s_debugLoggingEnabled;
|
||||
s_debugLoggingEnabled = enabled;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s_debugLoggingEnabled = _previous;
|
||||
}
|
||||
}
|
||||
|
||||
private static Pow2ModeScope UsePow2Mode(PrimeOrderHeuristicDevice device) => new(device);
|
||||
|
||||
private static DebugLoggingScope UseDebugLogging(bool enabled) => new(enabled);
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private static void DebugLog(string message)
|
||||
{
|
||||
if (s_debugLoggingEnabled)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private static void DebugLog(Func<string> messageFactory)
|
||||
{
|
||||
if (s_debugLoggingEnabled)
|
||||
{
|
||||
Console.WriteLine(messageFactory());
|
||||
}
|
||||
}
|
||||
private static UInt128 CalculateWideInternal(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (prime <= UInt128.One)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (prime == (UInt128)3UL)
|
||||
{
|
||||
return (UInt128)2UL;
|
||||
}
|
||||
|
||||
UInt128 phi = prime - UInt128.One;
|
||||
var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
||||
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
||||
UInt128 result;
|
||||
if (phiFactors.Factors is null)
|
||||
{
|
||||
result = FinishStrictlyWide(gpu, prime, divisorData);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = RunHeuristicPipelineWide(gpu, prime, previousOrder, divisorData, config, phi, phiFactors);
|
||||
}
|
||||
|
||||
debugScope.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static UInt128 RunHeuristicPipelineWide(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in UInt128 prime,
|
||||
in UInt128? previousOrder,
|
||||
in MontgomeryDivisorData? divisorData,
|
||||
in PrimeOrderSearchConfig config,
|
||||
in UInt128 phi,
|
||||
in PartialFactorResult128 phiFactors)
|
||||
{
|
||||
if (phiFactors.FullyFactored && TrySpecialMaxWide(gpu, phi, prime, divisorData, phiFactors))
|
||||
{
|
||||
return phi;
|
||||
}
|
||||
|
||||
UInt128 candidateOrder = InitializeStartingOrderWide(gpu, prime, phi, divisorData);
|
||||
candidateOrder = ExponentLoweringWide(gpu, candidateOrder, prime, divisorData, phiFactors);
|
||||
|
||||
if (TryConfirmOrderWide(gpu, prime, candidateOrder, divisorData, config))
|
||||
{
|
||||
return candidateOrder;
|
||||
}
|
||||
|
||||
if (config.Mode == PrimeOrderMode.Strict)
|
||||
{
|
||||
return FinishStrictlyWide(gpu, prime, divisorData);
|
||||
}
|
||||
|
||||
if (TryHeuristicFinishWide(gpu, prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
return candidateOrder;
|
||||
}
|
||||
|
||||
private static UInt128 FinishStrictlyWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in MontgomeryDivisorData? divisorData)
|
||||
{
|
||||
return CalculateByFactorizationWide(gpu, prime, divisorData);
|
||||
}
|
||||
|
||||
private static bool TrySpecialMaxWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData? divisorData, in PartialFactorResult128 factors)
|
||||
{
|
||||
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
||||
int length = factors.Count;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
UInt128 factor = factorSpan[i].Value;
|
||||
UInt128 reduced = phi / factor;
|
||||
if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UInt128 InitializeStartingOrderWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData? divisorData)
|
||||
{
|
||||
UInt128 order = phi;
|
||||
UInt128 mod8 = prime & (UInt128)7UL;
|
||||
if (mod8 == UInt128.One || mod8 == (UInt128)7UL)
|
||||
{
|
||||
UInt128 half = phi >> 1;
|
||||
if (Pow2ModWide(gpu, half, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = half;
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
private static UInt128 ExponentLoweringWide(PrimeOrderCalculatorAccelerator gpu, UInt128 order, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
|
||||
{
|
||||
ArrayPool<FactorEntry128> pool = ThreadStaticPools.FactorEntry128Pool;
|
||||
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
||||
int length = factors.Count;
|
||||
// TODO: This should never trigger from production code - check
|
||||
// if (length == 0)
|
||||
// {
|
||||
// return order;
|
||||
// }
|
||||
FactorEntry128[] tempArray = pool.Rent(length + 1);
|
||||
|
||||
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
|
||||
factorSpan.CopyTo(buffer);
|
||||
|
||||
if (!factors.FullyFactored && factors.Cofactor > UInt128.One && IsPrimeWide(factors.Cofactor))
|
||||
{
|
||||
buffer[length] = new FactorEntry128(factors.Cofactor, 1);
|
||||
length++;
|
||||
}
|
||||
|
||||
buffer.Slice(0, length).Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
UInt128 primeFactor = buffer[i].Value;
|
||||
int exponent = buffer[i].Exponent;
|
||||
for (int iteration = 0; iteration < exponent; iteration++)
|
||||
{
|
||||
if ((order % primeFactor) == UInt128.Zero)
|
||||
{
|
||||
UInt128 reduced = order / primeFactor;
|
||||
if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = reduced;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pool.Return(tempArray, clearArray: false);
|
||||
return order;
|
||||
}
|
||||
|
||||
private static bool TryConfirmOrderWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (order == UInt128.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Pow2ModWide(gpu, order, prime, divisorData) != UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PartialFactorResult128 factorization = PartialFactorWide(order, config);
|
||||
if (factorization.Factors is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!factorization.FullyFactored)
|
||||
{
|
||||
if (factorization.Cofactor <= UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPrimeWide(factorization.Cofactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
|
||||
}
|
||||
|
||||
ReadOnlySpan<FactorEntry128> span = factorization.Factors;
|
||||
int length = factorization.Count;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
UInt128 primeFactor = span[i].Value;
|
||||
UInt128 reduced = order;
|
||||
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
|
||||
{
|
||||
if ((reduced % primeFactor) != UInt128.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
reduced /= primeFactor;
|
||||
if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryHeuristicFinishWide(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in UInt128 prime,
|
||||
in UInt128 order,
|
||||
in UInt128? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
in PrimeOrderSearchConfig config,
|
||||
out UInt128 result)
|
||||
{
|
||||
result = UInt128.Zero;
|
||||
if (order <= UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PartialFactorResult128 orderFactors = PartialFactorWide(order, config);
|
||||
if (orderFactors.Factors is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!orderFactors.FullyFactored)
|
||||
{
|
||||
if (orderFactors.Cofactor <= UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPrimeWide(orderFactors.Cofactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
orderFactors = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
||||
}
|
||||
|
||||
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
|
||||
List<UInt128> candidates = new(capacity);
|
||||
FactorEntry128[] factorArray = orderFactors.Factors!;
|
||||
BuildCandidatesWide(order, factorArray, orderFactors.Count, candidates, capacity);
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SortCandidatesWide(prime, previousOrder, candidates);
|
||||
|
||||
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
|
||||
int powUsed = 0;
|
||||
int candidateCount = candidates.Count;
|
||||
|
||||
for (int i = 0; i < candidateCount; i++)
|
||||
{
|
||||
if (powUsed >= powBudget && powBudget > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
UInt128 candidate = candidates[i];
|
||||
powUsed++;
|
||||
|
||||
if (Pow2ModWide(gpu, candidate, prime, divisorData) != UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryConfirmCandidateWide(gpu, prime, candidate, config, ref powUsed, powBudget, divisorData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SortCandidatesWide(UInt128 prime, in UInt128? previousOrder, List<UInt128> candidates)
|
||||
{
|
||||
UInt128 previous = previousOrder ?? UInt128.Zero;
|
||||
@@ -539,59 +180,6 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryConfirmCandidateWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
|
||||
if (factorization.Factors is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!factorization.FullyFactored)
|
||||
{
|
||||
if (factorization.Cofactor <= UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPrimeWide(factorization.Cofactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
|
||||
}
|
||||
|
||||
ReadOnlySpan<FactorEntry128> span = factorization.Factors;
|
||||
int length = factorization.Count;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
UInt128 primeFactor = span[i].Value;
|
||||
UInt128 reduced = candidate;
|
||||
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
|
||||
{
|
||||
if ((reduced % primeFactor) != UInt128.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
reduced /= primeFactor;
|
||||
if (powUsed >= powBudget && powBudget > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
powUsed++;
|
||||
if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static PartialFactorResult128 PartialFactorWide(in UInt128 value, in PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (value <= UInt128.One)
|
||||
@@ -858,45 +446,6 @@ internal static partial class PrimeOrderCalculator
|
||||
return 64 + BitOperations.TrailingZeroCount(high);
|
||||
}
|
||||
|
||||
private static UInt128 CalculateByFactorizationWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in MontgomeryDivisorData? divisorData)
|
||||
{
|
||||
UInt128 phi = prime - UInt128.One;
|
||||
Dictionary<UInt128, int> counts = new(capacity: 8);
|
||||
FactorCompletelyWide(phi, counts);
|
||||
if (counts.Count == 0)
|
||||
{
|
||||
return phi;
|
||||
}
|
||||
|
||||
List<KeyValuePair<UInt128, int>> entries = new(counts);
|
||||
entries.Sort(static (a, b) => a.Key.CompareTo(b.Key));
|
||||
|
||||
UInt128 order = phi;
|
||||
int entryCount = entries.Count;
|
||||
for (int i = 0; i < entryCount; i++)
|
||||
{
|
||||
UInt128 primeFactor = entries[i].Key;
|
||||
int exponent = entries[i].Value;
|
||||
for (int iteration = 0; iteration < exponent; iteration++)
|
||||
{
|
||||
if ((order % primeFactor) != UInt128.Zero)
|
||||
{
|
||||
UInt128 candidate = order / primeFactor;
|
||||
if (Pow2ModWide(gpu, candidate, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = candidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
private static void FactorCompletelyWide(in UInt128 value, Dictionary<UInt128, int> counts)
|
||||
{
|
||||
if (value <= UInt128.One)
|
||||
@@ -1029,36 +578,6 @@ internal static partial class PrimeOrderCalculator
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UInt128 Pow2ModWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData? divisorData)
|
||||
{
|
||||
if (modulus == UInt128.One)
|
||||
{
|
||||
return UInt128.Zero;
|
||||
}
|
||||
|
||||
if (IsGpuPow2Allowed)
|
||||
{
|
||||
if (modulus <= (UInt128)ulong.MaxValue && exponent <= (UInt128)ulong.MaxValue)
|
||||
{
|
||||
ulong prime64 = (ulong)modulus;
|
||||
ulong exponent64 = (ulong)exponent;
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent64, prime64, out ulong remainder, divisorData);
|
||||
if (status == GpuPow2ModStatus.Success)
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
|
||||
GpuPow2ModStatus wideStatus = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, modulus, out UInt128 wideRemainder);
|
||||
if (wideStatus == GpuPow2ModStatus.Success)
|
||||
{
|
||||
return wideRemainder;
|
||||
}
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowed(modulus);
|
||||
}
|
||||
|
||||
private static void AddFactor(Dictionary<UInt128, int> counts, in UInt128 prime, int exponent)
|
||||
{
|
||||
if (counts.TryGetValue(prime, out int existing))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
@@ -17,21 +16,21 @@ internal static partial class PrimeOrderCalculator
|
||||
|
||||
public static PrimeOrderSearchConfig HeuristicDefault
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _heuristicDefault;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _heuristicDefault;
|
||||
}
|
||||
|
||||
public static readonly PrimeOrderSearchConfig StrictDefault = new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, PrimeOrderMode.Strict);
|
||||
|
||||
public static void ConfigureHeuristicDefault(int threadCount)
|
||||
{
|
||||
_heuristicDefault = CreateHeuristicDefault(threadCount);
|
||||
_heuristicDefault = CreateHeuristicDefault(threadCount);
|
||||
}
|
||||
|
||||
public static PrimeOrderSearchConfig CreateHeuristicDefault(int threadCount)
|
||||
{
|
||||
int normalizedThreadCount = threadCount < 1 ? 1 : threadCount;
|
||||
return new(smallFactorLimit: 100_000, pollardRhoMilliseconds: normalizedThreadCount * 24, maxPowChecks: 24, PrimeOrderMode.Heuristic);
|
||||
int normalizedThreadCount = threadCount < 1 ? 1 : threadCount;
|
||||
return new(smallFactorLimit: 100_000, pollardRhoMilliseconds: normalizedThreadCount * 24, maxPowChecks: 24, PrimeOrderMode.Heuristic);
|
||||
}
|
||||
|
||||
public readonly uint SmallFactorLimit = smallFactorLimit;
|
||||
@@ -40,68 +39,6 @@ internal static partial class PrimeOrderCalculator
|
||||
public readonly PrimeOrderMode Mode = mode;
|
||||
}
|
||||
|
||||
internal enum PrimeOrderHeuristicDevice
|
||||
{
|
||||
Cpu,
|
||||
Gpu,
|
||||
}
|
||||
|
||||
// TODO: Remove branching on the CPU / GPU paths
|
||||
// TODO: Split big non-static functions into smaller / extract static code to limit JIT time
|
||||
public static ulong Calculate(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong prime,
|
||||
ulong? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
in PrimeOrderSearchConfig config,
|
||||
PrimeOrderHeuristicDevice device)
|
||||
{
|
||||
var scope = UsePow2Mode(device);
|
||||
ulong order = CalculateInternal(gpu, prime, previousOrder, divisorData, config);
|
||||
scope.Dispose();
|
||||
return order;
|
||||
}
|
||||
|
||||
public static UInt128 Calculate(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in UInt128 prime,
|
||||
in UInt128? previousOrder,
|
||||
in PrimeOrderSearchConfig config,
|
||||
in PrimeOrderHeuristicDevice device)
|
||||
{
|
||||
var scope = UsePow2Mode(device);
|
||||
MontgomeryDivisorData divisorData;
|
||||
UInt128 result;
|
||||
if (prime <= ulong.MaxValue)
|
||||
{
|
||||
ulong? previous = null;
|
||||
if (previousOrder.HasValue)
|
||||
{
|
||||
UInt128 previousValue = previousOrder.Value;
|
||||
if (previousValue <= ulong.MaxValue)
|
||||
{
|
||||
previous = (ulong)previousValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
previous = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
ulong prime64 = (ulong)prime;
|
||||
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
|
||||
divisorData = divisorPool.FromModulus(prime64);
|
||||
ulong order64 = Calculate(gpu, prime64, previous, divisorData, config, device);
|
||||
divisorPool.Return(divisorData);
|
||||
result = order64 == 0UL ? UInt128.Zero : (UInt128)order64;
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorData = MontgomeryDivisorData.Empty;
|
||||
result = CalculateWideInternal(gpu, prime, previousOrder, divisorData, config);
|
||||
}
|
||||
|
||||
scope.Dispose();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,6 @@ namespace PerfectNumbers.Core
|
||||
}
|
||||
|
||||
_mersenneCpuDivisorSession = null;
|
||||
session.Reset();
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
@@ -517,7 +517,7 @@ public static class UInt128Extensions
|
||||
return (b1, a1);
|
||||
}
|
||||
|
||||
public static ulong CalculateOrder(this UInt128 q, PrimeOrderCalculatorAccelerator gpu)
|
||||
public static ulong CalculateOrderCpu(this UInt128 q)
|
||||
{
|
||||
if (q <= UInt128Numbers.Two)
|
||||
{
|
||||
@@ -536,7 +536,107 @@ public static class UInt128Extensions
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 cycle = MersenneDivisorCycles.GetCycle(gpu, q);
|
||||
UInt128 cycle = MersenneDivisorCycles.GetCycleCpu(q);
|
||||
// TODO: If the cache lacks this cycle, immediately schedule the configured device
|
||||
// (GPU by default) to compute it on the fly and skip inserting it into the cache so
|
||||
// wide-order factoring can still leverage cycle stepping without breaching the
|
||||
// memory cap or introducing extra synchronization.
|
||||
ulong prime, temp;
|
||||
for (; i < primesLength; i++)
|
||||
{
|
||||
if (smallPrimesPow2[i] > order)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
prime = smallPrimes[i];
|
||||
while (order % prime == 0UL)
|
||||
{
|
||||
temp = order / prime;
|
||||
if (temp.PowModWithCycle(q, cycle) == one)
|
||||
{
|
||||
order = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public static ulong CalculateOrderGpu(this UInt128 q, PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
if (q <= UInt128Numbers.Two)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
UInt128 one = UInt128.One;
|
||||
UInt128 phi = q - one;
|
||||
if (phi > ulong.MaxValue)
|
||||
{
|
||||
throw new NotImplementedException("Such big values are not yet supported");
|
||||
}
|
||||
|
||||
ulong order = (ulong)phi;
|
||||
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 cycle = MersenneDivisorCycles.GetCycleGpu(gpu, q);
|
||||
// TODO: If the cache lacks this cycle, immediately schedule the configured device
|
||||
// (GPU by default) to compute it on the fly and skip inserting it into the cache so
|
||||
// wide-order factoring can still leverage cycle stepping without breaching the
|
||||
// memory cap or introducing extra synchronization.
|
||||
ulong prime, temp;
|
||||
for (; i < primesLength; i++)
|
||||
{
|
||||
if (smallPrimesPow2[i] > order)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
prime = smallPrimes[i];
|
||||
while (order % prime == 0UL)
|
||||
{
|
||||
temp = order / prime;
|
||||
if (temp.PowModWithCycle(q, cycle) == one)
|
||||
{
|
||||
order = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public static ulong CalculateOrderHybrid(this UInt128 q, PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
if (q <= UInt128Numbers.Two)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
UInt128 one = UInt128.One;
|
||||
UInt128 phi = q - one;
|
||||
if (phi > ulong.MaxValue)
|
||||
{
|
||||
throw new NotImplementedException("Such big values are not yet supported");
|
||||
}
|
||||
|
||||
ulong order = (ulong)phi;
|
||||
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 cycle = MersenneDivisorCycles.GetCycleHybrid(gpu, q);
|
||||
// TODO: If the cache lacks this cycle, immediately schedule the configured device
|
||||
// (GPU by default) to compute it on the fly and skip inserting it into the cache so
|
||||
// wide-order factoring can still leverage cycle stepping without breaching the
|
||||
|
||||
@@ -5,263 +5,329 @@ namespace PerfectNumbers.Core;
|
||||
|
||||
public static partial class ULongExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWindowedCpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
// This should never happen in production code.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWindowedKeepMontgomeryCpu(this ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
// This should never happen in production code.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
|
||||
// }
|
||||
|
||||
// Small divisors come from the precomputed <= 4,000,000 snapshot and tiny rotation counts
|
||||
// appear when cycle reductions trigger. Route both cases through the single-bit ladder so the
|
||||
// CPU path avoids renting the window tables when a cheaper method is available.
|
||||
if (exponent <= Pow2WindowFallbackThreshold || modulus <= (ulong)PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
|
||||
}
|
||||
// Small divisors come from the precomputed <= 4,000,000 snapshot and tiny rotation counts
|
||||
// appear when cycle reductions trigger. Route both cases through the single-bit ladder so the
|
||||
// CPU path avoids renting the window tables when a cheaper method is available.
|
||||
if (exponent <= Pow2WindowFallbackThreshold || modulus <= (ulong)PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
return Pow2MontgomeryModSingleBitKeepMontgomery(exponent, divisor);
|
||||
}
|
||||
|
||||
int bitLength = GetPortableBitLengthCpu(exponent);
|
||||
int windowSize = GetWindowSizeCpu(bitLength);
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
int bitLength = GetPortableBitLengthCpu(exponent);
|
||||
int windowSize = GetWindowSizeCpu(bitLength);
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
|
||||
ulong[] oddPowersArray = ThreadStaticPools.UlongPool.Rent(oddPowerCount);
|
||||
Span<ulong> oddPowers = oddPowersArray.AsSpan(0, oddPowerCount);
|
||||
InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowers);
|
||||
ulong[] oddPowersArray = ThreadStaticPools.UlongPool.Rent(oddPowerCount);
|
||||
Span<ulong> oddPowers = oddPowersArray.AsSpan(0, oddPowerCount);
|
||||
InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowers);
|
||||
|
||||
int index = bitLength - 1;
|
||||
while (index >= 0)
|
||||
{
|
||||
if (((exponent >> index) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
int index = bitLength - 1;
|
||||
while (index >= 0)
|
||||
{
|
||||
if (((exponent >> index) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
int windowStart = index - windowSize + 1;
|
||||
if (windowStart < 0)
|
||||
{
|
||||
windowStart = 0;
|
||||
}
|
||||
int windowStart = index - windowSize + 1;
|
||||
if (windowStart < 0)
|
||||
{
|
||||
windowStart = 0;
|
||||
}
|
||||
|
||||
while (((exponent >> windowStart) & 1UL) == 0UL)
|
||||
{
|
||||
windowStart++;
|
||||
}
|
||||
while (((exponent >> windowStart) & 1UL) == 0UL)
|
||||
{
|
||||
windowStart++;
|
||||
}
|
||||
|
||||
int windowLength = index - windowStart + 1;
|
||||
for (int square = 0; square < windowLength; square++)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
}
|
||||
int windowLength = index - windowStart + 1;
|
||||
for (int square = 0; square < windowLength; square++)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
}
|
||||
|
||||
ulong mask = (1UL << windowLength) - 1UL;
|
||||
ulong windowValue = (exponent >> windowStart) & mask;
|
||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||
ulong multiplier = oddPowers[tableIndex];
|
||||
result = result.MontgomeryMultiplyCpu(multiplier, modulus, nPrime);
|
||||
ulong mask = (1UL << windowLength) - 1UL;
|
||||
ulong windowValue = (exponent >> windowStart) & mask;
|
||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||
ulong multiplier = oddPowers[tableIndex];
|
||||
result = result.MontgomeryMultiplyCpu(multiplier, modulus, nPrime);
|
||||
|
||||
index = windowStart - 1;
|
||||
}
|
||||
index = windowStart - 1;
|
||||
}
|
||||
|
||||
ThreadStaticPools.UlongPool.Return(oddPowersArray);
|
||||
ThreadStaticPools.UlongPool.Return(oddPowersArray);
|
||||
|
||||
if (!keepMontgomery)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWindowedConvertToStandardCpu(this ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
// This should never happen in production code.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
|
||||
// }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, Span<ulong> oddPowers)
|
||||
{
|
||||
oddPowers[0] = divisor.MontgomeryTwo;
|
||||
if (oddPowers.Length == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Small divisors come from the precomputed <= 4,000,000 snapshot and tiny rotation counts
|
||||
// appear when cycle reductions trigger. Route both cases through the single-bit ladder so the
|
||||
// CPU path avoids renting the window tables when a cheaper method is available.
|
||||
if (exponent <= Pow2WindowFallbackThreshold || modulus <= (ulong)PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
return Pow2MontgomeryModSingleBitConvertToStandard(exponent, divisor);
|
||||
}
|
||||
|
||||
ulong previous;
|
||||
ulong square = divisor.MontgomeryTwoSquared;
|
||||
int bitLength = GetPortableBitLengthCpu(exponent);
|
||||
int windowSize = GetWindowSizeCpu(bitLength);
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
|
||||
for (int i = 1; i < oddPowers.Length; i++)
|
||||
{
|
||||
previous = oddPowers[i - 1];
|
||||
oddPowers[i] = previous.MontgomeryMultiplyCpu(square, modulus, nPrime);
|
||||
}
|
||||
}
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWithCycleCpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong rotationCount = exponent % cycleLength;
|
||||
return Pow2MontgomeryModWindowedCpu(rotationCount, divisor, keepMontgomery: false);
|
||||
}
|
||||
ulong[] oddPowersArray = ThreadStaticPools.UlongPool.Rent(oddPowerCount);
|
||||
Span<ulong> oddPowers = oddPowersArray.AsSpan(0, oddPowerCount);
|
||||
InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowers);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModFromCycleRemainderCpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
return Pow2MontgomeryModWindowedCpu(reducedExponent, divisor, keepMontgomery: false);
|
||||
}
|
||||
int index = bitLength - 1;
|
||||
while (index >= 0)
|
||||
{
|
||||
if (((exponent >> index) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong CalculateMersenneDivisorCycleLengthUnrolledHexCpu(this ulong divisor)
|
||||
{
|
||||
// EvenPerfectBitScanner only routes odd divisors here; keep the guard commented out for benchmarks.
|
||||
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
// {
|
||||
// return 1UL;
|
||||
// }
|
||||
int windowStart = index - windowSize + 1;
|
||||
if (windowStart < 0)
|
||||
{
|
||||
windowStart = 0;
|
||||
}
|
||||
|
||||
ulong order = 1UL;
|
||||
ulong pow = 2UL;
|
||||
while (((exponent >> windowStart) & 1UL) == 0UL)
|
||||
{
|
||||
windowStart++;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
int windowLength = index - windowStart + 1;
|
||||
for (int square = 0; square < windowLength; square++)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
ulong mask = (1UL << windowLength) - 1UL;
|
||||
ulong windowValue = (exponent >> windowStart) & mask;
|
||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||
ulong multiplier = oddPowers[tableIndex];
|
||||
result = result.MontgomeryMultiplyCpu(multiplier, modulus, nPrime);
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
index = windowStart - 1;
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
ThreadStaticPools.UlongPool.Return(oddPowersArray);
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
result = result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, Span<ulong> oddPowers)
|
||||
{
|
||||
oddPowers[0] = divisor.MontgomeryTwo;
|
||||
if (oddPowers.Length == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
ulong previous;
|
||||
ulong square = divisor.MontgomeryTwoSquared;
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
for (int i = 1; i < oddPowers.Length; i++)
|
||||
{
|
||||
previous = oddPowers[i - 1];
|
||||
oddPowers[i] = previous.MontgomeryMultiplyCpu(square, modulus, nPrime);
|
||||
}
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWithCycleConvertToStandardCpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong rotationCount = exponent % cycleLength;
|
||||
return Pow2MontgomeryModWindowedConvertToStandardCpu(rotationCount, divisor);
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModFromCycleRemainderCpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
return Pow2MontgomeryModWindowedConvertToStandardCpu(reducedExponent, divisor);
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong CalculateMersenneDivisorCycleLengthUnrolledHexCpu(this ulong divisor)
|
||||
{
|
||||
// EvenPerfectBitScanner only routes odd divisors here; keep the guard commented out for benchmarks.
|
||||
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
// {
|
||||
// return 1UL;
|
||||
// }
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
ulong order = 1UL;
|
||||
ulong pow = 2UL;
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool AdvanceMersenneDivisorCycleStepCpu(ref ulong pow, ulong divisor, ref ulong order)
|
||||
{
|
||||
pow += pow;
|
||||
if (pow >= divisor)
|
||||
{
|
||||
pow -= divisor;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
order++;
|
||||
return pow == 1UL;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPortableBitLengthCpu(ulong value)
|
||||
{
|
||||
// Keep this commented out. It will never happen in production code.
|
||||
// if (value == 0UL)
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
return 64 - BitOperations.LeadingZeroCount(value);
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetWindowSizeCpu(int bitLength)
|
||||
{
|
||||
if (bitLength <= 6)
|
||||
{
|
||||
return Math.Max(bitLength, 1);
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (bitLength <= 23)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (bitLength <= 79)
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (bitLength <= 239)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (bitLength <= 671)
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
return PerfectNumberConstants.Pow2WindowSize;
|
||||
}
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
if (AdvanceMersenneDivisorCycleStepCpu(ref pow, divisor, ref order))
|
||||
{
|
||||
return order;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool AdvanceMersenneDivisorCycleStepCpu(ref ulong pow, ulong divisor, ref ulong order)
|
||||
{
|
||||
pow += pow;
|
||||
if (pow >= divisor)
|
||||
{
|
||||
pow -= divisor;
|
||||
}
|
||||
|
||||
order++;
|
||||
return pow == 1UL;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPortableBitLengthCpu(ulong value)
|
||||
{
|
||||
// Keep this commented out. It will never happen in production code.
|
||||
// if (value == 0UL)
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
return 64 - BitOperations.LeadingZeroCount(value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetWindowSizeCpu(int bitLength)
|
||||
{
|
||||
if (bitLength <= 6)
|
||||
{
|
||||
return Math.Max(bitLength, 1);
|
||||
}
|
||||
|
||||
if (bitLength <= 23)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (bitLength <= 79)
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (bitLength <= 239)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
|
||||
if (bitLength <= 671)
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
return PerfectNumberConstants.Pow2WindowSize;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -99,7 +99,7 @@ public static partial class ULongExtensions
|
||||
}
|
||||
}
|
||||
|
||||
public static ulong CalculateOrder(this ulong q, PrimeOrderCalculatorAccelerator gpu)
|
||||
public static ulong CalculateOrderCpu(this ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
{
|
||||
@@ -112,7 +112,101 @@ public static partial class ULongExtensions
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 q128 = q,
|
||||
cycle = MersenneDivisorCycles.GetCycle(gpu, q128);
|
||||
cycle = MersenneDivisorCycles.GetCycleCpu(q128);
|
||||
// TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
|
||||
// GPU computation (respecting the configured device) without promoting the result into
|
||||
// the cache so the order calculator still benefits from cycle stepping while keeping the
|
||||
// single-block memory plan intact.
|
||||
|
||||
for (; i < primesLength; i++)
|
||||
{
|
||||
if (smallPrimesPow2[i] > order)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
prime = smallPrimes[i];
|
||||
// TODO: Replace this `%` driven factor peeling with the divisor-cycle aware
|
||||
// factoring helper so large orders reuse the cached remainders highlighted in
|
||||
// the latest divisor-cycle benchmarks instead of recomputing slow modulo checks.
|
||||
while (order % prime == 0UL)
|
||||
{
|
||||
temp = order / prime;
|
||||
if (temp.PowModWithCycle(q128, cycle) == UInt128.One)
|
||||
{
|
||||
order = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public static ulong CalculateOrderGpu(this ulong q, PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong order = q - 1UL, prime, temp;
|
||||
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 q128 = q,
|
||||
cycle = MersenneDivisorCycles.GetCycleGpu(gpu, q128);
|
||||
// TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
|
||||
// GPU computation (respecting the configured device) without promoting the result into
|
||||
// the cache so the order calculator still benefits from cycle stepping while keeping the
|
||||
// single-block memory plan intact.
|
||||
|
||||
for (; i < primesLength; i++)
|
||||
{
|
||||
if (smallPrimesPow2[i] > order)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
prime = smallPrimes[i];
|
||||
// TODO: Replace this `%` driven factor peeling with the divisor-cycle aware
|
||||
// factoring helper so large orders reuse the cached remainders highlighted in
|
||||
// the latest divisor-cycle benchmarks instead of recomputing slow modulo checks.
|
||||
while (order % prime == 0UL)
|
||||
{
|
||||
temp = order / prime;
|
||||
if (temp.PowModWithCycle(q128, cycle) == UInt128.One)
|
||||
{
|
||||
order = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public static ulong CalculateOrderHybrid(this ulong q, PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong order = q - 1UL, prime, temp;
|
||||
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 q128 = q,
|
||||
cycle = MersenneDivisorCycles.GetCycleHybrid(gpu, q128);
|
||||
// TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
|
||||
// GPU computation (respecting the configured device) without promoting the result into
|
||||
// the cache so the order calculator still benefits from cycle stepping while keeping the
|
||||
@@ -542,13 +636,13 @@ public static partial class ULongExtensions
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// Reuse the direct UInt128 reduction path that dominated MulMod64Benchmarks to avoid
|
||||
// the redundant operand modulo operations in the older fallback.
|
||||
public static ulong MulMod64(this ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
return (ulong)(((UInt128)a * b) % modulus);
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// Reuse the direct UInt128 reduction path that dominated MulMod64Benchmarks to avoid
|
||||
// the redundant operand modulo operations in the older fallback.
|
||||
public static ulong MulMod64(this ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
return (ulong)(((UInt128)a * b) % modulus);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong MontgomeryMultiplyCpu(this ulong a, ulong b, ulong modulus, ulong nPrime)
|
||||
@@ -566,8 +660,8 @@ public static partial class ULongExtensions
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Pow2MontgomeryModSingleBit(ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Pow2MontgomeryModSingleBitKeepMontgomery(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
@@ -591,268 +685,295 @@ public static partial class ULongExtensions
|
||||
baseVal = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
}
|
||||
|
||||
return keepMontgomery ? result : result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Pow2MontgomeryModSingleBitConvertToStandard(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
ulong remainingExponent = exponent;
|
||||
|
||||
while (remainingExponent != 0UL)
|
||||
{
|
||||
if ((remainingExponent & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
}
|
||||
|
||||
remainingExponent >>= 1;
|
||||
if (remainingExponent == 0UL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
baseVal = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
||||
{
|
||||
// EvenPerfectBitScanner starts CPU scans at p >= 138,000,000 (Program.cs),
|
||||
// so production never routes exponent == 0 here. Keep the guard commented out
|
||||
// for targeted micro-benchmarks.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return UInt128.One;
|
||||
// }
|
||||
|
||||
// MersenneResidueAutomaton seeds q = 2 * p * k + 1 (ModuloAutomata.cs),
|
||||
// making modulus >= 2 * p + 1 > 276,000,000 and odd on the CPU path. The
|
||||
// modulus == 1 guard is therefore unreachable during EvenPerfectBitScanner
|
||||
// runs.
|
||||
// if (modulus == UInt128.One)
|
||||
// {
|
||||
// return UInt128.Zero;
|
||||
// }
|
||||
|
||||
// Production exponents stay above 64 bits and modulus never drops below 4
|
||||
// for admissible divisors, so the classic doubling fallback does not
|
||||
// trigger in scans.
|
||||
// if (exponent < 64 || modulus < 4)
|
||||
// {
|
||||
// return Pow2ByDoubling(exponent, modulus);
|
||||
// }
|
||||
|
||||
// Every admissible divisor q is odd (2 * p * k + 1), preventing the
|
||||
// power-of-two mask path from activating in production.
|
||||
// if ((modulus & (modulus - 1)) == 0)
|
||||
// {
|
||||
// UInt128 masked = UInt128.One << (int)(exponent & 127);
|
||||
// return masked & (modulus - 1);
|
||||
// }
|
||||
|
||||
if (!ShouldUseWindowedPow2(modulus))
|
||||
{
|
||||
return Pow2ByDoubling(exponent, modulus);
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowed(modulus);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using a known cycle length.
|
||||
/// </summary>
|
||||
|
||||
public static UInt128 PowModWithCycle(this ulong exponent, UInt128 modulus, ulong cycleLength)
|
||||
{
|
||||
// EvenPerfectBitScanner keeps exponent >= 138,000,000 on CPU (Program.cs),
|
||||
// so the zero-exponent guard stays disabled outside tests.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return UInt128.One;
|
||||
// }
|
||||
|
||||
// The divisor generator produces odd q = 2 * p * k + 1 (ModuloAutomata.cs),
|
||||
// so modulus never reaches 1 in production.
|
||||
// if (modulus == UInt128.One)
|
||||
// {
|
||||
// return UInt128.Zero;
|
||||
// }
|
||||
|
||||
// Every admissible divisor is odd, so the power-of-two shortcut cannot
|
||||
// trigger while scanning real candidates.
|
||||
// if ((modulus & (modulus - 1)) == 0)
|
||||
// {
|
||||
// UInt128 masked = UInt128.One << (int)(exponent & 127);
|
||||
// return masked & (modulus - 1);
|
||||
// }
|
||||
|
||||
bool hasCycle = cycleLength != 0UL;
|
||||
// TODO: Replace this modulo with the cached cycle remainder produced by the divisor-cycle cache so PowModWithCycle avoids
|
||||
// repeated `%` work, matching the ProcessEightBitWindows wins captured in Pow2MontgomeryModCycleComputationBenchmarks.
|
||||
ulong rotationCount = hasCycle && exponent >= cycleLength ? exponent % cycleLength : exponent;
|
||||
|
||||
// Keep this fast path: valid divisors satisfy cycle | exponent, producing
|
||||
// a zero remainder that means 2^exponent ≡ 1 (mod q).
|
||||
if (hasCycle && rotationCount == 0UL)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
// FastDiv64 can return tiny divisors (see MersenneNumberIncrementalCpuTester),
|
||||
// so the small-rotation fallback still fires on the CPU path.
|
||||
if (modulus < 4 || rotationCount < 64)
|
||||
{
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
if (ShouldUseWindowedPow2(modulus))
|
||||
{
|
||||
UInt128 exponent128 = exponent;
|
||||
UInt128 rotationCount128 = rotationCount;
|
||||
return exponent128.Pow2MontgomeryModWindowedWithCycle(modulus, cycleLength, rotationCount128);
|
||||
}
|
||||
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using a known cycle length.
|
||||
/// </summary>
|
||||
|
||||
public static UInt128 PowModWithCycle(this ulong exponent, UInt128 modulus, UInt128 cycleLength)
|
||||
{
|
||||
// CPU scans only feed exponents >= 138,000,000 (Program.cs), so keep the
|
||||
// zero-exponent shortcut commented out for tests.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return UInt128.One;
|
||||
// }
|
||||
|
||||
// Modulus originates from q = 2 * p * k + 1 (ModuloAutomata.cs), therefore
|
||||
// modulus == 1 never occurs outside dedicated benchmarks.
|
||||
// if (modulus == UInt128.One)
|
||||
// {
|
||||
// return UInt128.Zero;
|
||||
// }
|
||||
|
||||
// Admissible q are odd, rendering the power-of-two shortcut unreachable.
|
||||
// if ((modulus & (modulus - UInt128.One)) == UInt128.Zero)
|
||||
// {
|
||||
// UInt128 masked = UInt128.One << (int)(exponent & UInt128Numbers.OneHundredTwentySeven);
|
||||
// return masked & (modulus - UInt128.One);
|
||||
// }
|
||||
|
||||
bool hasCycle = cycleLength != UInt128.Zero;
|
||||
UInt128 exponent128 = exponent;
|
||||
// TODO: Swap this modulo with the upcoming UInt128 cycle remainder helper so large-exponent scans reuse cached
|
||||
// reductions instead of recomputing `%` for every lookup, as demonstrated in Pow2MontgomeryModCycleComputationBenchmarks.
|
||||
UInt128 rotationCount = hasCycle && exponent128 >= cycleLength ? exponent128 % cycleLength : exponent128;
|
||||
|
||||
if (hasCycle && rotationCount == UInt128.Zero)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (modulus < UInt128Numbers.Four || rotationCount < UInt128Numbers.SixtyFour)
|
||||
{
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
if (ShouldUseWindowedPow2(modulus))
|
||||
{
|
||||
return exponent128.Pow2MontgomeryModWindowedWithCycle(modulus, cycleLength, rotationCount);
|
||||
}
|
||||
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using a known cycle length.
|
||||
/// </summary>
|
||||
|
||||
public static UInt128 PowModWithCycle(this UInt128 exponent, UInt128 modulus, UInt128 cycleLength)
|
||||
{
|
||||
UInt128 one = UInt128.One;
|
||||
UInt128 zero = UInt128.Zero;
|
||||
|
||||
// EvenPerfectBitScanner drives this path with exponents derived from p, phi,
|
||||
// or divisor quotients, none of which are zero on the production scan.
|
||||
// if (exponent == zero)
|
||||
// {
|
||||
// return one;
|
||||
// }
|
||||
|
||||
// Modulus again comes from q = 2 * p * k + 1, so modulus == 1 is unreachable.
|
||||
// if (modulus == one)
|
||||
// {
|
||||
// return zero;
|
||||
// }
|
||||
|
||||
// Candidate divisors stay odd, therefore the power-of-two shortcut stays off.
|
||||
// if ((modulus & (modulus - one)) == zero)
|
||||
// {
|
||||
// UInt128 masked = one << (int)(exponent & UInt128Numbers.OneHundredTwentySeven);
|
||||
// return masked & (modulus - one);
|
||||
// }
|
||||
|
||||
bool hasCycle = cycleLength != zero;
|
||||
// TODO: Swap this modulo with the shared UInt128 cycle remainder helper once available so CRT powmods reuse cached
|
||||
// reductions in the windowed ladder, avoiding the `%` cost highlighted in Pow2MontgomeryModCycleComputationBenchmarks.
|
||||
UInt128 rotationCount = hasCycle && exponent >= cycleLength ? exponent % cycleLength : exponent;
|
||||
|
||||
if (hasCycle && rotationCount == zero)
|
||||
{
|
||||
return one;
|
||||
}
|
||||
|
||||
if (modulus < UInt128Numbers.Four || rotationCount < UInt128Numbers.SixtyFour)
|
||||
{
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
if (ShouldUseWindowedPow2(modulus))
|
||||
{
|
||||
return exponent.Pow2MontgomeryModWindowedWithCycle(modulus, cycleLength, rotationCount);
|
||||
}
|
||||
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
||||
{
|
||||
// EvenPerfectBitScanner starts CPU scans at p >= 138,000,000 (Program.cs),
|
||||
// so production never routes exponent == 0 here. Keep the guard commented out
|
||||
// for targeted micro-benchmarks.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return UInt128.One;
|
||||
// }
|
||||
private static bool ShouldUseWindowedPow2(UInt128 modulus) => modulus > Pow2WindowedModulusThreshold;
|
||||
|
||||
// MersenneResidueAutomaton seeds q = 2 * p * k + 1 (ModuloAutomata.cs),
|
||||
// making modulus >= 2 * p + 1 > 276,000,000 and odd on the CPU path. The
|
||||
// modulus == 1 guard is therefore unreachable during EvenPerfectBitScanner
|
||||
// runs.
|
||||
// if (modulus == UInt128.One)
|
||||
// {
|
||||
// return UInt128.Zero;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static UInt128 Pow2ByDoubling(ulong exponent, UInt128 modulus)
|
||||
{
|
||||
UInt128 result = UInt128.One;
|
||||
for (ulong i = 0; i < exponent; i++)
|
||||
{
|
||||
result <<= 1;
|
||||
while (result >= modulus)
|
||||
{
|
||||
result -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
// Production exponents stay above 64 bits and modulus never drops below 4
|
||||
// for admissible divisors, so the classic doubling fallback does not
|
||||
// trigger in scans.
|
||||
// if (exponent < 64 || modulus < 4)
|
||||
// {
|
||||
// return Pow2ByDoubling(exponent, modulus);
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
// Every admissible divisor q is odd (2 * p * k + 1), preventing the
|
||||
// power-of-two mask path from activating in production.
|
||||
// if ((modulus & (modulus - 1)) == 0)
|
||||
// {
|
||||
// UInt128 masked = UInt128.One << (int)(exponent & 127);
|
||||
// return masked & (modulus - 1);
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static UInt128 Pow2ByDoubling(UInt128 exponent, UInt128 modulus)
|
||||
{
|
||||
UInt128 result = UInt128.One;
|
||||
UInt128 index = UInt128.Zero;
|
||||
|
||||
if (!ShouldUseWindowedPow2(modulus))
|
||||
{
|
||||
return Pow2ByDoubling(exponent, modulus);
|
||||
}
|
||||
while (index < exponent)
|
||||
{
|
||||
result <<= 1;
|
||||
while (result >= modulus)
|
||||
{
|
||||
result -= modulus;
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowed(modulus);
|
||||
}
|
||||
index += UInt128.One;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using a known cycle length.
|
||||
/// </summary>
|
||||
|
||||
public static UInt128 PowModWithCycle(this ulong exponent, UInt128 modulus, ulong cycleLength)
|
||||
{
|
||||
// EvenPerfectBitScanner keeps exponent >= 138,000,000 on CPU (Program.cs),
|
||||
// so the zero-exponent guard stays disabled outside tests.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return UInt128.One;
|
||||
// }
|
||||
|
||||
// The divisor generator produces odd q = 2 * p * k + 1 (ModuloAutomata.cs),
|
||||
// so modulus never reaches 1 in production.
|
||||
// if (modulus == UInt128.One)
|
||||
// {
|
||||
// return UInt128.Zero;
|
||||
// }
|
||||
|
||||
// Every admissible divisor is odd, so the power-of-two shortcut cannot
|
||||
// trigger while scanning real candidates.
|
||||
// if ((modulus & (modulus - 1)) == 0)
|
||||
// {
|
||||
// UInt128 masked = UInt128.One << (int)(exponent & 127);
|
||||
// return masked & (modulus - 1);
|
||||
// }
|
||||
|
||||
bool hasCycle = cycleLength != 0UL;
|
||||
// TODO: Replace this modulo with the cached cycle remainder produced by the divisor-cycle cache so PowModWithCycle avoids
|
||||
// repeated `%` work, matching the ProcessEightBitWindows wins captured in Pow2MontgomeryModCycleComputationBenchmarks.
|
||||
ulong rotationCount = hasCycle && exponent >= cycleLength ? exponent % cycleLength : exponent;
|
||||
|
||||
// Keep this fast path: valid divisors satisfy cycle | exponent, producing
|
||||
// a zero remainder that means 2^exponent ≡ 1 (mod q).
|
||||
if (hasCycle && rotationCount == 0UL)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
// FastDiv64 can return tiny divisors (see MersenneNumberIncrementalCpuTester),
|
||||
// so the small-rotation fallback still fires on the CPU path.
|
||||
if (modulus < 4 || rotationCount < 64)
|
||||
{
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
if (ShouldUseWindowedPow2(modulus))
|
||||
{
|
||||
UInt128 exponent128 = exponent;
|
||||
UInt128 rotationCount128 = rotationCount;
|
||||
return exponent128.Pow2MontgomeryModWindowedWithCycle(modulus, cycleLength, rotationCount128);
|
||||
}
|
||||
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using a known cycle length.
|
||||
/// </summary>
|
||||
|
||||
public static UInt128 PowModWithCycle(this ulong exponent, UInt128 modulus, UInt128 cycleLength)
|
||||
{
|
||||
// CPU scans only feed exponents >= 138,000,000 (Program.cs), so keep the
|
||||
// zero-exponent shortcut commented out for tests.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return UInt128.One;
|
||||
// }
|
||||
|
||||
// Modulus originates from q = 2 * p * k + 1 (ModuloAutomata.cs), therefore
|
||||
// modulus == 1 never occurs outside dedicated benchmarks.
|
||||
// if (modulus == UInt128.One)
|
||||
// {
|
||||
// return UInt128.Zero;
|
||||
// }
|
||||
|
||||
// Admissible q are odd, rendering the power-of-two shortcut unreachable.
|
||||
// if ((modulus & (modulus - UInt128.One)) == UInt128.Zero)
|
||||
// {
|
||||
// UInt128 masked = UInt128.One << (int)(exponent & UInt128Numbers.OneHundredTwentySeven);
|
||||
// return masked & (modulus - UInt128.One);
|
||||
// }
|
||||
|
||||
bool hasCycle = cycleLength != UInt128.Zero;
|
||||
UInt128 exponent128 = exponent;
|
||||
// TODO: Swap this modulo with the upcoming UInt128 cycle remainder helper so large-exponent scans reuse cached
|
||||
// reductions instead of recomputing `%` for every lookup, as demonstrated in Pow2MontgomeryModCycleComputationBenchmarks.
|
||||
UInt128 rotationCount = hasCycle && exponent128 >= cycleLength ? exponent128 % cycleLength : exponent128;
|
||||
|
||||
if (hasCycle && rotationCount == UInt128.Zero)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (modulus < UInt128Numbers.Four || rotationCount < UInt128Numbers.SixtyFour)
|
||||
{
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
if (ShouldUseWindowedPow2(modulus))
|
||||
{
|
||||
return exponent128.Pow2MontgomeryModWindowedWithCycle(modulus, cycleLength, rotationCount);
|
||||
}
|
||||
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using a known cycle length.
|
||||
/// </summary>
|
||||
|
||||
public static UInt128 PowModWithCycle(this UInt128 exponent, UInt128 modulus, UInt128 cycleLength)
|
||||
{
|
||||
UInt128 one = UInt128.One;
|
||||
UInt128 zero = UInt128.Zero;
|
||||
|
||||
// EvenPerfectBitScanner drives this path with exponents derived from p, phi,
|
||||
// or divisor quotients, none of which are zero on the production scan.
|
||||
// if (exponent == zero)
|
||||
// {
|
||||
// return one;
|
||||
// }
|
||||
|
||||
// Modulus again comes from q = 2 * p * k + 1, so modulus == 1 is unreachable.
|
||||
// if (modulus == one)
|
||||
// {
|
||||
// return zero;
|
||||
// }
|
||||
|
||||
// Candidate divisors stay odd, therefore the power-of-two shortcut stays off.
|
||||
// if ((modulus & (modulus - one)) == zero)
|
||||
// {
|
||||
// UInt128 masked = one << (int)(exponent & UInt128Numbers.OneHundredTwentySeven);
|
||||
// return masked & (modulus - one);
|
||||
// }
|
||||
|
||||
bool hasCycle = cycleLength != zero;
|
||||
// TODO: Swap this modulo with the shared UInt128 cycle remainder helper once available so CRT powmods reuse cached
|
||||
// reductions in the windowed ladder, avoiding the `%` cost highlighted in Pow2MontgomeryModCycleComputationBenchmarks.
|
||||
UInt128 rotationCount = hasCycle && exponent >= cycleLength ? exponent % cycleLength : exponent;
|
||||
|
||||
if (hasCycle && rotationCount == zero)
|
||||
{
|
||||
return one;
|
||||
}
|
||||
|
||||
if (modulus < UInt128Numbers.Four || rotationCount < UInt128Numbers.SixtyFour)
|
||||
{
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
if (ShouldUseWindowedPow2(modulus))
|
||||
{
|
||||
return exponent.Pow2MontgomeryModWindowedWithCycle(modulus, cycleLength, rotationCount);
|
||||
}
|
||||
|
||||
return Pow2ByDoubling(rotationCount, modulus);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool ShouldUseWindowedPow2(UInt128 modulus) => modulus > Pow2WindowedModulusThreshold;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static UInt128 Pow2ByDoubling(ulong exponent, UInt128 modulus)
|
||||
{
|
||||
UInt128 result = UInt128.One;
|
||||
for (ulong i = 0; i < exponent; i++)
|
||||
{
|
||||
result <<= 1;
|
||||
while (result >= modulus)
|
||||
{
|
||||
result -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static UInt128 Pow2ByDoubling(UInt128 exponent, UInt128 modulus)
|
||||
{
|
||||
UInt128 result = UInt128.One;
|
||||
UInt128 index = UInt128.Zero;
|
||||
|
||||
while (index < exponent)
|
||||
{
|
||||
result <<= 1;
|
||||
while (result >= modulus)
|
||||
{
|
||||
result -= modulus;
|
||||
}
|
||||
|
||||
index += UInt128.One;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using iterative CRT composition from mod 10 up to modulus.
|
||||
/// Only for modulus >= 10 and reasonable size.
|
||||
/// </summary>
|
||||
public static UInt128 PowModCrt(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, UInt128 modulus, MersenneDivisorCycles cycles)
|
||||
public static UInt128 PowModCrtCpu(this ulong exponent, UInt128 modulus)
|
||||
{
|
||||
if (modulus < 10)
|
||||
return PowMod(exponent, modulus); // fallback to classic
|
||||
@@ -867,7 +988,95 @@ public static partial class ULongExtensions
|
||||
|
||||
for (; modulusCandidate <= modulus; modulusCandidate++)
|
||||
{
|
||||
cycle = MersenneDivisorCycles.GetCycle(gpu, modulusCandidate);
|
||||
cycle = MersenneDivisorCycles.GetCycleCpu(modulusCandidate);
|
||||
remainderForCandidate = cycle > zero
|
||||
? PowModWithCycle(exponent, modulusCandidate, cycle)
|
||||
: PowMod(exponent, modulusCandidate);
|
||||
|
||||
// Solve x ≡ result mod currentModulus
|
||||
// x ≡ remM mod m
|
||||
// Find x mod (currentModulus * m)
|
||||
// Since currentModulus and m are coprime, use CRT:
|
||||
// x = result + currentModulus * t, where t ≡ (remM - result) * inv(currentModulus, m) mod m
|
||||
|
||||
// TODO: Replace this `% modulusCandidate` with the cached residue helper derived from Mod10_8_5_3Benchmarks so CRT
|
||||
// composition avoids repeated modulo divisions when combining residues for large divisor sets.
|
||||
result += currentModulus * ((remainderForCandidate + modulusCandidate - (result % modulusCandidate)) * ModInverse(currentModulus, modulusCandidate) % modulusCandidate);
|
||||
currentModulus *= modulusCandidate;
|
||||
|
||||
if (currentModulus >= modulus)
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Swap this final `% modulus` with the pooled remainder cache so the CRT result write-back avoids one more division,
|
||||
// aligning with the optimizations captured in Mod10_8_5_3Benchmarks.
|
||||
return result % modulus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using iterative CRT composition from mod 10 up to modulus.
|
||||
/// Only for modulus >= 10 and reasonable size.
|
||||
/// </summary>
|
||||
public static UInt128 PowModCrtGpu(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, UInt128 modulus)
|
||||
{
|
||||
if (modulus < 10)
|
||||
return PowMod(exponent, modulus); // fallback to classic
|
||||
|
||||
// Use cycle length 4 for mod 10
|
||||
UInt128 currentModulus = 10,
|
||||
cycle,
|
||||
modulusCandidate = 11,
|
||||
remainderForCandidate,
|
||||
result = PowModWithCycle(exponent, 10, 4),
|
||||
zero = UInt128.Zero;
|
||||
|
||||
for (; modulusCandidate <= modulus; modulusCandidate++)
|
||||
{
|
||||
cycle = MersenneDivisorCycles.GetCycleGpu(gpu, modulusCandidate);
|
||||
remainderForCandidate = cycle > zero
|
||||
? PowModWithCycle(exponent, modulusCandidate, cycle)
|
||||
: PowMod(exponent, modulusCandidate);
|
||||
|
||||
// Solve x ≡ result mod currentModulus
|
||||
// x ≡ remM mod m
|
||||
// Find x mod (currentModulus * m)
|
||||
// Since currentModulus and m are coprime, use CRT:
|
||||
// x = result + currentModulus * t, where t ≡ (remM - result) * inv(currentModulus, m) mod m
|
||||
|
||||
// TODO: Replace this `% modulusCandidate` with the cached residue helper derived from Mod10_8_5_3Benchmarks so CRT
|
||||
// composition avoids repeated modulo divisions when combining residues for large divisor sets.
|
||||
result += currentModulus * ((remainderForCandidate + modulusCandidate - (result % modulusCandidate)) * ModInverse(currentModulus, modulusCandidate) % modulusCandidate);
|
||||
currentModulus *= modulusCandidate;
|
||||
|
||||
if (currentModulus >= modulus)
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Swap this final `% modulus` with the pooled remainder cache so the CRT result write-back avoids one more division,
|
||||
// aligning with the optimizations captured in Mod10_8_5_3Benchmarks.
|
||||
return result % modulus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes 2^exponent mod modulus using iterative CRT composition from mod 10 up to modulus.
|
||||
/// Only for modulus >= 10 and reasonable size.
|
||||
/// </summary>
|
||||
public static UInt128 PowModCrtHybrid(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, UInt128 modulus)
|
||||
{
|
||||
if (modulus < 10)
|
||||
return PowMod(exponent, modulus); // fallback to classic
|
||||
|
||||
// Use cycle length 4 for mod 10
|
||||
UInt128 currentModulus = 10,
|
||||
cycle,
|
||||
modulusCandidate = 11,
|
||||
remainderForCandidate,
|
||||
result = PowModWithCycle(exponent, 10, 4),
|
||||
zero = UInt128.Zero;
|
||||
|
||||
for (; modulusCandidate <= modulus; modulusCandidate++)
|
||||
{
|
||||
cycle = MersenneDivisorCycles.GetCycleHybrid(gpu, modulusCandidate);
|
||||
remainderForCandidate = cycle > zero
|
||||
? PowModWithCycle(exponent, modulusCandidate, cycle)
|
||||
: PowMod(exponent, modulusCandidate);
|
||||
@@ -927,7 +1136,7 @@ public static partial class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool SharesFactorWithExponentMinusOne(this ulong exponent, PrimeOrderCalculatorAccelerator gpu)
|
||||
public static bool SharesFactorWithExponentMinusOneCpu(this ulong exponent)
|
||||
{
|
||||
ulong prime, value = exponent - 1UL;
|
||||
value >>= BitOperations.TrailingZeroCount(value);
|
||||
@@ -943,7 +1152,7 @@ public static partial class ULongExtensions
|
||||
continue;
|
||||
}
|
||||
|
||||
if (exponent % prime.CalculateOrder(gpu) == 0UL)
|
||||
if (exponent % prime.CalculateOrderCpu() == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -955,7 +1164,81 @@ public static partial class ULongExtensions
|
||||
while (value % prime == 0UL);
|
||||
}
|
||||
|
||||
if (value > 1UL && exponent % value.CalculateOrder(gpu) == 0UL)
|
||||
if (value > 1UL && exponent % value.CalculateOrderCpu() == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool SharesFactorWithExponentMinusOneGpu(this ulong exponent, PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
ulong prime, value = exponent - 1UL;
|
||||
value >>= BitOperations.TrailingZeroCount(value);
|
||||
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
int i = 1, smallPrimesLength = smallPrimes.Length;
|
||||
|
||||
for (; i < smallPrimesLength && smallPrimesPow2[i] <= value; i++)
|
||||
{
|
||||
prime = smallPrimes[i];
|
||||
if (value % prime != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (exponent % prime.CalculateOrderGpu(gpu) == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
value /= prime;
|
||||
}
|
||||
while (value % prime == 0UL);
|
||||
}
|
||||
|
||||
if (value > 1UL && exponent % value.CalculateOrderGpu(gpu) == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool SharesFactorWithExponentMinusOneHybrid(this ulong exponent, PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
ulong prime, value = exponent - 1UL;
|
||||
value >>= BitOperations.TrailingZeroCount(value);
|
||||
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
int i = 1, smallPrimesLength = smallPrimes.Length;
|
||||
|
||||
for (; i < smallPrimesLength && smallPrimesPow2[i] <= value; i++)
|
||||
{
|
||||
prime = smallPrimes[i];
|
||||
if (value % prime != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (exponent % prime.CalculateOrderHybrid(gpu) == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
value /= prime;
|
||||
}
|
||||
while (value % prime == 0UL);
|
||||
}
|
||||
|
||||
if (value > 1UL && exponent % value.CalculateOrderHybrid(gpu) == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user