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
@@ -28,18 +28,18 @@ public class Pow2ModBenchmarks
|
||||
private const long OneShiftedLeft60 = 1L << 60;
|
||||
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
||||
|
||||
private MontgomeryDivisorData[] _smallDivisors = Array.Empty<MontgomeryDivisorData>();
|
||||
private MontgomeryDivisorData[] _largeDivisors = Array.Empty<MontgomeryDivisorData>();
|
||||
private MontgomeryDivisorData[] _veryLargeDivisors = Array.Empty<MontgomeryDivisorData>();
|
||||
private UInt128[] _wideModuli = Array.Empty<UInt128>();
|
||||
private UInt128[] _wideExponents = Array.Empty<UInt128>();
|
||||
private ulong[] _smallExponents = Array.Empty<ulong>();
|
||||
private ulong[] _largeExponents = Array.Empty<ulong>();
|
||||
private ulong[] _veryLargeExponents = Array.Empty<ulong>();
|
||||
private ulong[] _smallCycles = Array.Empty<ulong>();
|
||||
private ulong[] _largeCycles = Array.Empty<ulong>();
|
||||
private ulong[] _veryLargeCycles = Array.Empty<ulong>();
|
||||
private UInt128[] _wideCycles = Array.Empty<UInt128>();
|
||||
private MontgomeryDivisorData[] _smallDivisors = [];
|
||||
private MontgomeryDivisorData[] _largeDivisors = [];
|
||||
private MontgomeryDivisorData[] _veryLargeDivisors = [];
|
||||
private UInt128[] _wideModuli = [];
|
||||
private UInt128[] _wideExponents = [];
|
||||
private ulong[] _smallExponents = [];
|
||||
private ulong[] _largeExponents = [];
|
||||
private ulong[] _veryLargeExponents = [];
|
||||
private ulong[] _smallCycles = [];
|
||||
private ulong[] _largeCycles = [];
|
||||
private ulong[] _veryLargeCycles = [];
|
||||
private UInt128[] _wideCycles = [];
|
||||
private ulong? _previousPrimeOrder;
|
||||
private UInt128? _previousWidePrimeOrder;
|
||||
|
||||
@@ -82,7 +82,7 @@ public class Pow2ModBenchmarks
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertToStandardCpu(divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -120,7 +120,7 @@ public class Pow2ModBenchmarks
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleConvertToStandardCpu(cycles[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -162,7 +162,7 @@ public class Pow2ModBenchmarks
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleConvertToStandardCpu(cycle, divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -200,8 +200,8 @@ public class Pow2ModBenchmarks
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = CalculateCycleLengthWithHeuristics(gpu, divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||
ulong cycle = CalculateCycleLengthWithHeuristicsCpu(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleConvertToStandardCpu(cycle, divisors[i]);
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
@@ -221,7 +221,7 @@ public class Pow2ModBenchmarks
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = CalculateCycleLengthWithHeuristics(gpu, divisors[i].Modulus);
|
||||
ulong cycle = CalculateCycleLengthWithHeuristicsGpu(gpu, divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycle, divisors[i]);
|
||||
}
|
||||
|
||||
@@ -230,14 +230,30 @@ public class Pow2ModBenchmarks
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public UInt128 HeuristicWideCycleComputation()
|
||||
public UInt128 HeuristicWideCycleComputationCpu()
|
||||
{
|
||||
UInt128 checksum = UInt128.Zero;
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
UInt128 cycle = CalculateCycleLengthWithHeuristics(gpu, _wideModuli[i]);
|
||||
UInt128 cycle = CalculateCycleLengthWithHeuristicsCpu(_wideModuli[i]);
|
||||
checksum ^= cycle;
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public UInt128 HeuristicWideCycleComputationGpu()
|
||||
{
|
||||
UInt128 checksum = UInt128.Zero;
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
UInt128 cycle = CalculateCycleLengthWithHeuristicsGpu(gpu, _wideModuli[i]);
|
||||
checksum ^= cycle;
|
||||
}
|
||||
|
||||
@@ -484,7 +500,7 @@ public class Pow2ModBenchmarks
|
||||
Console.WriteLine($"Calculating small cycle {smallModulus}");
|
||||
smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
smallExponents[i] = NextSmallExponent(random);
|
||||
smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(gpu, smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus));
|
||||
smallCycles[i] = MersenneDivisorCycles.CalculateCycleLengthCpu(smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus));
|
||||
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle(random);
|
||||
Console.WriteLine($"Calculating large cycle {largeModulus}");
|
||||
@@ -496,13 +512,13 @@ public class Pow2ModBenchmarks
|
||||
Console.WriteLine($"Calculating very large cycle {veryLargeModulus} with heuristics");
|
||||
veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus);
|
||||
veryLargeExponents[i] = NextVeryLargeExponent(heuristicRandom);
|
||||
veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(gpu, veryLargeModulus, ref previousPrimeOrder);
|
||||
veryLargeCycles[i] = CalculateCycleLengthWithHeuristicsCpu(veryLargeModulus, ref previousPrimeOrder);
|
||||
|
||||
UInt128 wideModulus = NextWideOddModulus(wideRandom);
|
||||
Console.WriteLine($"Calculating wide cycle {wideModulus} with heuristics");
|
||||
wideModuli[i] = wideModulus;
|
||||
wideExponents[i] = NextWideExponent(wideRandom);
|
||||
wideCycles[i] = CalculateCycleLengthWithHeuristics(gpu, wideModulus, ref previousWidePrimeOrder);
|
||||
wideCycles[i] = CalculateCycleLengthWithHeuristicsCpu(wideModulus, ref previousWidePrimeOrder);
|
||||
}
|
||||
|
||||
SampleCollections collections = new SampleCollections(
|
||||
@@ -595,21 +611,19 @@ public class Pow2ModBenchmarks
|
||||
return ((UInt128)high << 64) | low;
|
||||
}
|
||||
|
||||
private static ulong CalculateCycleLengthWithHeuristics(PrimeOrderCalculatorAccelerator gpu, ulong modulus, ref ulong? previousPrimeOrder)
|
||||
private static ulong CalculateCycleLengthWithHeuristicsCpu(ulong modulus, ref ulong? previousPrimeOrder)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(modulus);
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisorData);
|
||||
return MersenneDivisorCycles.CalculateCycleLengthCpu(modulus, divisorData);
|
||||
}
|
||||
|
||||
ulong order = PrimeOrderCalculator.Calculate(
|
||||
gpu,
|
||||
ulong order = PrimeOrderCalculator.CalculateCpu(
|
||||
modulus,
|
||||
previousPrimeOrder,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
if (order != 0UL)
|
||||
{
|
||||
@@ -618,22 +632,20 @@ public class Pow2ModBenchmarks
|
||||
}
|
||||
|
||||
previousPrimeOrder = null;
|
||||
return MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisorData);
|
||||
return MersenneDivisorCycles.CalculateCycleLengthCpu(modulus, divisorData);
|
||||
}
|
||||
|
||||
private static UInt128 CalculateCycleLengthWithHeuristics(PrimeOrderCalculatorAccelerator gpu, UInt128 modulus, ref UInt128? previousWidePrimeOrder)
|
||||
private static UInt128 CalculateCycleLengthWithHeuristicsCpu(UInt128 modulus, ref UInt128? previousWidePrimeOrder)
|
||||
{
|
||||
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
|
||||
{
|
||||
return MersenneDivisorCycles.GetCycle(gpu, modulus);
|
||||
return MersenneDivisorCycles.GetCycleCpu(modulus);
|
||||
}
|
||||
|
||||
UInt128 order = PrimeOrderCalculator.Calculate(
|
||||
gpu,
|
||||
UInt128 order = PrimeOrderCalculator.CalculateCpu(
|
||||
modulus,
|
||||
previousWidePrimeOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
if (order != UInt128.Zero)
|
||||
{
|
||||
@@ -642,7 +654,7 @@ public class Pow2ModBenchmarks
|
||||
}
|
||||
|
||||
previousWidePrimeOrder = null;
|
||||
return MersenneDivisorCycles.GetCycle(gpu, modulus);
|
||||
return MersenneDivisorCycles.GetCycleCpu(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,24 +678,22 @@ public class Pow2ModBenchmarks
|
||||
montgomeryTwoSquared);
|
||||
}
|
||||
|
||||
private ulong CalculateCycleLengthWithHeuristics(PrimeOrderCalculatorAccelerator gpu, ulong modulus, ulong fallbackOrder = 0UL)
|
||||
private ulong CalculateCycleLengthWithHeuristicsCpu(ulong modulus, ulong fallbackOrder = 0UL)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(modulus);
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisorData);
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLengthCpu(modulus, divisorData);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine("Trying heuristic. Prime order calculation");
|
||||
#endif
|
||||
ulong order = PrimeOrderCalculator.Calculate(
|
||||
gpu,
|
||||
ulong order = PrimeOrderCalculator.CalculateCpu(
|
||||
modulus,
|
||||
_previousPrimeOrder,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
if (order != 0UL)
|
||||
{
|
||||
@@ -694,25 +704,53 @@ public class Pow2ModBenchmarks
|
||||
_previousPrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisorData);
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLengthCpu(modulus, divisorData);
|
||||
}
|
||||
|
||||
private UInt128 CalculateCycleLengthWithHeuristics(PrimeOrderCalculatorAccelerator gpu, UInt128 modulus)
|
||||
private ulong CalculateCycleLengthWithHeuristicsGpu(PrimeOrderCalculatorAccelerator gpu, ulong modulus, ulong fallbackOrder = 0UL)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(modulus);
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, modulus, divisorData);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine("Trying heuristic. Prime order calculation");
|
||||
#endif
|
||||
ulong order = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
modulus,
|
||||
_previousPrimeOrder,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
if (order != 0UL)
|
||||
{
|
||||
_previousPrimeOrder = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
_previousPrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, modulus, divisorData);
|
||||
}
|
||||
|
||||
private UInt128 CalculateCycleLengthWithHeuristicsCpu(UInt128 modulus)
|
||||
{
|
||||
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
|
||||
{
|
||||
return MersenneDivisorCycles.GetCycle(gpu, modulus);
|
||||
return MersenneDivisorCycles.GetCycleCpu(modulus);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
||||
#endif
|
||||
UInt128 order = PrimeOrderCalculator.Calculate(
|
||||
gpu,
|
||||
UInt128 order = PrimeOrderCalculator.CalculateCpu(
|
||||
modulus,
|
||||
_previousWidePrimeOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
if (order != UInt128.Zero)
|
||||
{
|
||||
@@ -723,7 +761,35 @@ public class Pow2ModBenchmarks
|
||||
_previousWidePrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return MersenneDivisorCycles.GetCycle(gpu, modulus);
|
||||
return MersenneDivisorCycles.GetCycleCpu(modulus);
|
||||
}
|
||||
|
||||
private UInt128 CalculateCycleLengthWithHeuristicsGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 modulus)
|
||||
{
|
||||
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
|
||||
{
|
||||
return MersenneDivisorCycles.GetCycleGpu(gpu, modulus);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
||||
#endif
|
||||
UInt128 order = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
modulus,
|
||||
_previousWidePrimeOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
if (order != UInt128.Zero)
|
||||
{
|
||||
_previousWidePrimeOrder = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
_previousWidePrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return MersenneDivisorCycles.GetCycleGpu(gpu, modulus);
|
||||
}
|
||||
|
||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
||||
|
||||
@@ -39,7 +39,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
ulong smallModulus = NextSmallOddModulus();
|
||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
_smallExponents[i] = NextSmallExponent();
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(gpu, smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus));
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus));
|
||||
|
||||
Console.WriteLine($"Calculating large cycle {i + 1} with heuristics");
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||
@@ -62,7 +62,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertToStandardCpu(divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -100,7 +100,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleConvertToStandardCpu(cycles[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
|
||||
@@ -67,7 +67,7 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
||||
Pow2MontgomeryModWindowedBenchmarkSeed seed = seeds[i];
|
||||
ulong modulus = 2UL * seed.Multiplier * seed.Exponent + 1UL;
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorDataPool.Shared.FromModulus(modulus);
|
||||
ulong cycleLength = MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisor);
|
||||
ulong cycleLength = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, modulus, divisor);
|
||||
ulong reducedExponent = seed.Exponent % cycleLength;
|
||||
cases[i] = new Pow2MontgomeryModWindowedBenchmarkCase(
|
||||
seed.Exponent,
|
||||
|
||||
+3
-3
@@ -32,7 +32,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedCpu(current.Divisor, keepMontgomery: false);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(current.Divisor);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -50,7 +50,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedCpu(current.Divisor, keepMontgomery: true);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepMontgomeryCpu(current.Divisor);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -68,7 +68,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryCpuBenchmarks
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWithCycleCpu(current.CycleLength, current.Divisor);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWithCycleConvertToStandardCpu(current.CycleLength, current.Divisor);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
|
||||
@@ -542,7 +542,7 @@ public class ProgramTests
|
||||
|
||||
string output = writer.ToString();
|
||||
output.Should().Contain("Usage:");
|
||||
output.Should().Contain("--mersenne-device=cpu|gpu");
|
||||
output.Should().Contain("--mersenne-device=cpu|gpu|hybrid");
|
||||
output.Should().Contain("--primes-device=cpu|gpu");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ internal static class CandidatesCalculator
|
||||
return trackers.Value!;
|
||||
}
|
||||
|
||||
internal static bool IsCompositeByResidues(PrimeOrderCalculatorAccelerator gpu, ulong p)
|
||||
internal static bool IsCompositeByResiduesCpu(ulong p)
|
||||
{
|
||||
ModResidueTracker tracker = AcquireResidueTracker();
|
||||
// Use ModResidueTracker with a small set of primes to pre-filter composite p.
|
||||
@@ -72,7 +72,57 @@ internal static class CandidatesCalculator
|
||||
break;
|
||||
}
|
||||
|
||||
if (tracker.MergeOrAppend(gpu, p, primes[primeIndex], out bool divisible) && divisible)
|
||||
if (tracker.MergeOrAppendCpu(p, primes[primeIndex], out bool divisible) && divisible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsCompositeByResiduesGpu(PrimeOrderCalculatorAccelerator gpu, ulong p)
|
||||
{
|
||||
ModResidueTracker tracker = AcquireResidueTracker();
|
||||
// Use ModResidueTracker with a small set of primes to pre-filter composite p.
|
||||
tracker.BeginMerge(p);
|
||||
// TODO: Integrate the divisor-cycle cache here so the small-prime sweep reuses precomputed remainders instead of
|
||||
// running MergeOrAppend for every candidate and missing the cycle-accelerated early exits.
|
||||
// Use the small prime list from PerfectNumbers.Core to the extent of sqrt(p)
|
||||
var primes = PrimesGenerator.SmallPrimes;
|
||||
var primesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
int len = primes.Length;
|
||||
for (int primeIndex = 0; primeIndex < len; primeIndex++)
|
||||
{
|
||||
if (primesPow2[primeIndex] > p)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (tracker.MergeOrAppendGpu(gpu, p, primes[primeIndex], out bool divisible) && divisible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsCompositeByResiduesHybrid(PrimeOrderCalculatorAccelerator gpu, ulong p)
|
||||
{
|
||||
ModResidueTracker tracker = AcquireResidueTracker();
|
||||
tracker.BeginMerge(p);
|
||||
var primes = PrimesGenerator.SmallPrimes;
|
||||
var primesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
int len = primes.Length;
|
||||
for (int primeIndex = 0; primeIndex < len; primeIndex++)
|
||||
{
|
||||
if (primesPow2[primeIndex] > p)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (tracker.MergeOrAppendHybrid(gpu, p, primes[primeIndex], out bool divisible) && divisible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ internal readonly struct CliArguments
|
||||
internal readonly bool UseGcdFilter;
|
||||
internal readonly bool TestMode;
|
||||
internal readonly bool UseGpuCycles;
|
||||
internal readonly bool UseMersenneOnGpu;
|
||||
internal readonly bool UseOrderOnGpu;
|
||||
internal readonly ComputationDevice MersenneDevice;
|
||||
internal readonly ComputationDevice OrderDevice;
|
||||
internal readonly int ScanBatchSize;
|
||||
internal readonly int SliceSize;
|
||||
internal readonly ulong ResidueKMax;
|
||||
@@ -72,8 +72,8 @@ internal readonly struct CliArguments
|
||||
bool useGcdFilter,
|
||||
bool testMode,
|
||||
bool useGpuCycles,
|
||||
bool useMersenneOnGpu,
|
||||
bool useOrderOnGpu,
|
||||
ComputationDevice mersenneDevice,
|
||||
ComputationDevice orderDevice,
|
||||
int scanBatchSize,
|
||||
int sliceSize,
|
||||
ulong residueKMax,
|
||||
@@ -116,8 +116,8 @@ internal readonly struct CliArguments
|
||||
UseGcdFilter = useGcdFilter;
|
||||
TestMode = testMode;
|
||||
UseGpuCycles = useGpuCycles;
|
||||
UseMersenneOnGpu = useMersenneOnGpu;
|
||||
UseOrderOnGpu = useOrderOnGpu;
|
||||
MersenneDevice = mersenneDevice;
|
||||
OrderDevice = orderDevice;
|
||||
ScanBatchSize = scanBatchSize;
|
||||
SliceSize = sliceSize;
|
||||
ResidueKMax = residueKMax;
|
||||
@@ -165,8 +165,8 @@ internal readonly struct CliArguments
|
||||
bool useGcdFilter = false;
|
||||
bool testMode = false;
|
||||
bool useGpuCycles = true;
|
||||
bool useMersenneOnGpu = true;
|
||||
bool useOrderOnGpu = true;
|
||||
ComputationDevice mersenneDevice = ComputationDevice.Gpu;
|
||||
ComputationDevice orderDevice = ComputationDevice.Gpu;
|
||||
int scanBatchSize = 2_097_152;
|
||||
int sliceSize = 32;
|
||||
ulong residueKMax = 5_000_000UL;
|
||||
@@ -319,7 +319,8 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useMersenneOnGpu = !argument.AsSpan("--mersenne-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--mersenne-device=".Length);
|
||||
mersenneDevice = ParseDevice(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -389,7 +390,8 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useOrderOnGpu = !argument.AsSpan("--order-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--order-device=".Length);
|
||||
orderDevice = ParseDevice(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -445,7 +447,7 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--lucas=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useMersenneOnGpu = !argument.AsSpan("--lucas=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
mersenneDevice = ParseDevice(argument.AsSpan("--lucas=".Length));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -565,8 +567,8 @@ internal readonly struct CliArguments
|
||||
useGcdFilter,
|
||||
testMode,
|
||||
useGpuCycles,
|
||||
useMersenneOnGpu,
|
||||
useOrderOnGpu,
|
||||
mersenneDevice,
|
||||
orderDevice,
|
||||
scanBatchSize,
|
||||
sliceSize,
|
||||
residueKMax,
|
||||
@@ -603,10 +605,10 @@ internal readonly struct CliArguments
|
||||
Console.WriteLine(" --block-size=<value> values processed per thread batch");
|
||||
Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method");
|
||||
Console.WriteLine(" --residue-max-k=<value> max k for residue Mersenne test (q = 2*p*k + 1)");
|
||||
Console.WriteLine(" --mersenne-device=cpu|gpu Device for Mersenne method (default gpu)");
|
||||
Console.WriteLine(" --mersenne-device=cpu|gpu|hybrid Device for Mersenne method (default gpu)");
|
||||
Console.WriteLine(" --primes-device=cpu|gpu Device for prime-scan kernels (default gpu)");
|
||||
Console.WriteLine(" --gpu-prime-batch=<n> Batch size for GPU primality sieve (default 262144)");
|
||||
Console.WriteLine(" --order-device=cpu|gpu Device for order computations (default gpu)");
|
||||
Console.WriteLine(" --order-device=cpu|gpu|hybrid Device for order computations (default gpu)");
|
||||
Console.WriteLine(" --ntt=reference|staged GPU NTT backend (default staged)");
|
||||
Console.WriteLine(" --mod-reduction=auto|uint128|mont64|barrett128 staged NTT reduction (default auto)");
|
||||
Console.WriteLine(" --gpu-prime-threads=<value> max concurrent GPU prime checks (default 1)");
|
||||
@@ -632,4 +634,19 @@ internal readonly struct CliArguments
|
||||
Console.WriteLine(" --min-k=<value> starting k for --mersenne=bydivisor divisor scan (default 1)");
|
||||
Console.WriteLine(" --help, -help, --?, -?, /? show this help message");
|
||||
}
|
||||
|
||||
private static ComputationDevice ParseDevice(ReadOnlySpan<char> value)
|
||||
{
|
||||
if (value.Equals("cpu", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ComputationDevice.Cpu;
|
||||
}
|
||||
|
||||
if (value.Equals("hybrid", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ComputationDevice.Hybrid;
|
||||
}
|
||||
|
||||
return ComputationDevice.Gpu;
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,11 @@ internal static class Program
|
||||
? PrimeTransformMode.Bit
|
||||
: (useResidue ? PrimeTransformMode.Add : PrimeTransformMode.AddPrimes);
|
||||
CandidatesCalculator.Configure(transformMode);
|
||||
bool mersenneOnGpu = _cliArguments.UseMersenneOnGpu;
|
||||
bool orderOnGpu = _cliArguments.UseOrderOnGpu;
|
||||
ComputationDevice mersenneDevice = _cliArguments.MersenneDevice;
|
||||
ComputationDevice orderDevice = _cliArguments.OrderDevice;
|
||||
bool mersenneOnGpu = mersenneDevice == ComputationDevice.Gpu;
|
||||
bool orderOnGpu = orderDevice == ComputationDevice.Gpu;
|
||||
bool orderOnHybrid = orderDevice == ComputationDevice.Hybrid;
|
||||
int scanBatchSize = Math.Max(1, _cliArguments.ScanBatchSize);
|
||||
int sliceSize = Math.Max(1, _cliArguments.SliceSize);
|
||||
ulong residueKMax = _cliArguments.ResidueKMax;
|
||||
@@ -165,7 +168,6 @@ internal static class Program
|
||||
}
|
||||
|
||||
DivisorCycleCache.SetDivisorCyclesBatchSize(cyclesBatchSize);
|
||||
DivisorCycleCache.Shared.ConfigureGeneratorDevice(useGpuCycles);
|
||||
// TODO: Keep a single cached block loaded from disk and honor the configured device when
|
||||
// computing ad-hoc cycles for divisors that fall outside that snapshot instead of queuing
|
||||
// generation of additional blocks.
|
||||
@@ -194,13 +196,25 @@ internal static class Program
|
||||
useOrder: useOrder,
|
||||
useGpuLucas: mersenneOnGpu,
|
||||
useGpuScan: mersenneOnGpu,
|
||||
useGpuOrder: orderOnGpu,
|
||||
useGpuOrder: orderOnGpu || orderOnHybrid,
|
||||
useResidue: useResidue,
|
||||
maxK: residueKMax);
|
||||
maxK: residueKMax,
|
||||
orderDevice: orderDevice);
|
||||
if (!useLucas)
|
||||
{
|
||||
Console.WriteLine("Warming up orders");
|
||||
tester.WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
if (orderDevice == ComputationDevice.Hybrid)
|
||||
{
|
||||
tester.WarmUpOrdersHybrid(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
else if (orderDevice == ComputationDevice.Gpu)
|
||||
{
|
||||
tester.WarmUpOrdersGpu(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
else
|
||||
{
|
||||
tester.WarmUpOrdersCpu(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
}
|
||||
|
||||
return tester;
|
||||
@@ -216,7 +230,7 @@ internal static class Program
|
||||
{
|
||||
_byDivisorTester = mersenneOnGpu
|
||||
? new MersenneNumberDivisorByDivisorGpuTester()
|
||||
: new MersenneNumberDivisorByDivisorCpuTester();
|
||||
: new MersenneNumberDivisorByDivisorCpuTester(orderDevice);
|
||||
_byDivisorTester.MinK = _cliArguments.MinK;
|
||||
_byDivisorTester.BatchSize = scanBatchSize;
|
||||
}
|
||||
@@ -245,6 +259,20 @@ internal static class Program
|
||||
|
||||
Console.WriteLine("Initialization...");
|
||||
// Compose a results file name that encodes configuration (before opening file)
|
||||
string mersenneDeviceLabel = mersenneDevice switch
|
||||
{
|
||||
ComputationDevice.Hybrid => "hybrid",
|
||||
ComputationDevice.Gpu => "gpu",
|
||||
_ => "cpu",
|
||||
};
|
||||
|
||||
string orderDeviceLabel = orderDevice switch
|
||||
{
|
||||
ComputationDevice.Hybrid => "hybrid",
|
||||
ComputationDevice.Gpu => "gpu",
|
||||
_ => "cpu",
|
||||
};
|
||||
|
||||
var builtName = CalculationResultsFile.BuildFileName(
|
||||
useBitTransform,
|
||||
threadCount,
|
||||
@@ -257,14 +285,14 @@ internal static class Program
|
||||
useOrder,
|
||||
_cliArguments.UseGcdFilter,
|
||||
NttGpuMath.GpuTransformBackend,
|
||||
gpuPrimeThreads,
|
||||
sliceSize,
|
||||
scanBatchSize,
|
||||
orderWarmupLimitOverride ?? 5_000_000UL,
|
||||
NttGpuMath.ReductionMode,
|
||||
mersenneOnGpu ? "gpu" : "cpu",
|
||||
_runPrimesOnCpu ? "cpu" : "gpu",
|
||||
orderOnGpu ? "gpu" : "cpu");
|
||||
gpuPrimeThreads,
|
||||
sliceSize,
|
||||
scanBatchSize,
|
||||
orderWarmupLimitOverride ?? 5_000_000UL,
|
||||
NttGpuMath.ReductionMode,
|
||||
mersenneDeviceLabel,
|
||||
_runPrimesOnCpu ? "cpu" : "gpu",
|
||||
orderDeviceLabel);
|
||||
|
||||
if (!string.IsNullOrEmpty(_cliArguments.ResultsPrefix))
|
||||
{
|
||||
@@ -447,7 +475,18 @@ internal static class Program
|
||||
if (!useLucas)
|
||||
{
|
||||
Console.WriteLine("Warming up orders...");
|
||||
MersenneTesters.Value!.WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
if (orderDevice == ComputationDevice.Hybrid)
|
||||
{
|
||||
MersenneTesters.Value!.WarmUpOrdersHybrid(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
else if (orderDevice == ComputationDevice.Gpu)
|
||||
{
|
||||
MersenneTesters.Value!.WarmUpOrdersGpu(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
else
|
||||
{
|
||||
MersenneTesters.Value!.WarmUpOrdersCpu(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,7 +658,12 @@ internal static class Program
|
||||
}
|
||||
|
||||
// Fast residue-based composite check for p using small primes
|
||||
if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor && CandidatesCalculator.IsCompositeByResidues(gpu, p))
|
||||
if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor &&
|
||||
(
|
||||
(_cliArguments.OrderDevice == ComputationDevice.Gpu && CandidatesCalculator.IsCompositeByResiduesGpu(gpu, p)) ||
|
||||
(_cliArguments.OrderDevice == ComputationDevice.Hybrid && CandidatesCalculator.IsCompositeByResiduesHybrid(gpu, p)) ||
|
||||
(_cliArguments.OrderDevice == ComputationDevice.Cpu && CandidatesCalculator.IsCompositeByResiduesCpu(p))
|
||||
))
|
||||
{
|
||||
searchedMersenne = false;
|
||||
detailedCheck = false;
|
||||
|
||||
@@ -398,7 +398,7 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
|
||||
ulong result = PrimeOrderCalculator.Calculate(gpu, prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
ulong result = PrimeOrderCalculator.CalculateGpu(gpu, prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
result.Should().Be(3UL);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class ULongExtensionsGpuTests
|
||||
public void Pow2ModWindowedGpu_matches_montgomery_cpu_for_known_values(ulong exponent, ulong modulus)
|
||||
{
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorDataPool.Shared.FromModulus(modulus);
|
||||
ulong expected = exponent.Pow2MontgomeryModWindowedCpu(divisor, keepMontgomery: false);
|
||||
ulong expected = exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisor);
|
||||
|
||||
exponent.Pow2ModWindowedGpu(modulus).Should().Be(expected);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class ULongExtensionsGpuTests
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorDataPool.Shared.FromModulus(modulus);
|
||||
foreach (ulong exponent in exponents)
|
||||
{
|
||||
ulong expected = exponent.Pow2MontgomeryModWindowedCpu(divisor, keepMontgomery: false);
|
||||
ulong expected = exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisor);
|
||||
exponent.Pow2ModWindowedGpu(modulus).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
{
|
||||
expected.Should().Contain(pair.divisor);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(gpu, pair.divisor, divisorData));
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLengthCpu(pair.divisor, divisorData));
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -59,7 +59,6 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
public void Generate_produces_unique_cycles_for_expected_divisors()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
|
||||
@@ -92,7 +91,7 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
{
|
||||
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(gpu, pair.divisor, divisorData));
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLengthCpu(pair.divisor, divisorData));
|
||||
}
|
||||
|
||||
uniqueDivisors.Should().BeEquivalentTo(expected);
|
||||
@@ -102,7 +101,6 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
|
||||
@@ -23,7 +23,7 @@ public class MersenneDivisorCyclesTests
|
||||
try
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(divisor);
|
||||
MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData).Should().Be(expected);
|
||||
MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData).Should().Be(expected);
|
||||
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -46,17 +46,17 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||
ulong[] primes = [5UL, 7UL, 11UL, 13UL];
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(gpu, 23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL));
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLengthCpu(23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL));
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL), cycle23, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
hits.Should().ContainInOrder([0, 0, 1, 0]);
|
||||
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(gpu, 31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL));
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLengthCpu(31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL));
|
||||
session.CheckDivisor(31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL), cycle31, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||
hits.Should().ContainInOrder([1, 0, 0, 0]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -76,14 +76,14 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
ulong[] exponents = [6UL, 7UL, 9UL, 10UL, 12UL];
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 7UL, divisorData);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthCpu(7UL, divisorData);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
hits.Should().Equal([1, 0, 1, 0, 1]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -103,14 +103,14 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
ulong[] exponents = [10UL, 11UL, 20UL, 21UL, 30UL];
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 11UL, divisorData);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthCpu(11UL, divisorData);
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
hits.Should().Equal([1, 0, 1, 0, 1]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -128,7 +128,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
|
||||
tester.DivisorLimit.Should().Be(0UL);
|
||||
|
||||
ulong[] primes = { 31UL, 37UL };
|
||||
ulong[] primes = [31UL, 37UL];
|
||||
ulong[] allowed = new ulong[primes.Length];
|
||||
|
||||
tester.PrepareCandidates(primes, allowed);
|
||||
@@ -228,12 +228,12 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 31UL, 37UL, 41UL };
|
||||
ulong[] primes = [31UL, 37UL, 41UL];
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), 0UL, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
|
||||
hits.Should().ContainInOrder([0, 1, 0]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -253,17 +253,17 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
|
||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL];
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
|
||||
hits.Should().ContainInOrder([0, 1, 0, 0]);
|
||||
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(gpu, 13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL));
|
||||
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL));
|
||||
session.CheckDivisor(13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL), cycle13367, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
hits.Should().ContainInOrder([0, 0, 1, 0]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -287,13 +287,13 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL, 47UL];
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
hits.Should().ContainInOrder([0, 1, 0, 0, 0]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -316,15 +316,15 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
ulong[] exponents = [6UL, 7UL, 9UL, 10UL, 12UL];
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 7UL, divisorData);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 7UL, divisorData);
|
||||
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
hits.Should().Equal([1, 0, 1, 0, 1]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -347,15 +347,15 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
ulong[] exponents = [10UL, 11UL, 20UL, 21UL, 30UL];
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 11UL, divisorData);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 11UL, divisorData);
|
||||
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
hits.Should().Equal([1, 0, 1, 0, 1]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -382,7 +382,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(191UL);
|
||||
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(gpu, 191UL, divisorData);
|
||||
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 191UL, divisorData);
|
||||
|
||||
tableField.SetValue(cycles, patchedTable);
|
||||
smallCyclesField.SetValue(cycles, patchedSmall);
|
||||
@@ -423,13 +423,13 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var session = tester.CreateDivisorSession(gpu);
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL, 47UL];
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
hits.Should().ContainInOrder([0, 1, 0, 0, 0]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -25,15 +25,8 @@ public class MersenneNumberResidueCpuTesterTests
|
||||
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -7,27 +8,36 @@ namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberResidueGpuTesterTests
|
||||
{
|
||||
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(bool useGpuOrder)
|
||||
{
|
||||
var tester = new MersenneNumberResidueGpuTester(useGpuOrder);
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Scan_handles_various_prime_exponents(bool useGpuOrder)
|
||||
{
|
||||
var tester = new MersenneNumberResidueGpuTester(useGpuOrder);
|
||||
|
||||
RunCase(tester, 23UL, 2UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
||||
RunCase(tester, 31UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_001UL, expectedPrime: true);
|
||||
}
|
||||
RunCase(tester, 23UL, 2UL, expectedPrime: false);
|
||||
RunCase(tester, 29UL, 37UL, expectedPrime: false);
|
||||
RunCase(tester, 31UL, 1_000UL, expectedPrime: true);
|
||||
RunCase(tester, 89UL, 1_001UL, expectedPrime: true);
|
||||
RunCase(tester, 127UL, 1_001UL, expectedPrime: true);
|
||||
}
|
||||
|
||||
private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
private static void RunCase(MersenneNumberResidueGpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
|
||||
{
|
||||
bool isPrime = true;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
|
||||
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,27 @@ public class MersenneNumberTesterTests
|
||||
|
||||
[Theory]
|
||||
[InlineData(125UL)]
|
||||
public void WarmUpOrders_populates_cache_without_affecting_results(ulong p)
|
||||
public void WarmUpOrdersCpu_populates_cache_without_affecting_results(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true);
|
||||
tester.WarmUpOrders(p, 1_000UL);
|
||||
tester.WarmUpOrdersCpu(p, 1_000UL);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tester.IsMersennePrime(gpu, p).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(125UL)]
|
||||
public void WarmUpOrdersGpu_populates_cache_without_affecting_results(ulong p)
|
||||
{
|
||||
var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true);
|
||||
tester.WarmUpOrdersGpu(p, 1_000UL);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
@@ -45,12 +62,20 @@ public class MersenneNumberTesterTests
|
||||
[Theory]
|
||||
[InlineData(125UL, true)]
|
||||
[InlineData(127UL, false)]
|
||||
public void SharesFactorWithExponentMinusOne_detects_common_factors(ulong p, bool expected)
|
||||
public void SharesFactorWithExponentMinusOneCpu_detects_common_factors(ulong p, bool expected)
|
||||
{
|
||||
p.SharesFactorWithExponentMinusOneCpu().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(125UL, true)]
|
||||
[InlineData(127UL, false)]
|
||||
public void SharesFactorWithExponentMinusOneGpu_detects_common_factors(ulong p, bool expected)
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
p.SharesFactorWithExponentMinusOne(gpu).Should().Be(expected);
|
||||
p.SharesFactorWithExponentMinusOneGpu(gpu).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -8,16 +8,26 @@ namespace PerfectNumbers.Core.Tests;
|
||||
public class ModResidueTrackerTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsDivisible_advances_residue_for_identity()
|
||||
public void IsDivisibleCpu_advances_residue_for_identity()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
tracker.IsDivisibleCpu(5, 2).Should().BeFalse();
|
||||
tracker.IsDivisibleCpu(6, 2).Should().BeTrue();
|
||||
tracker.IsDivisibleCpu(7, 2).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDivisibleGpu_advances_residue_for_identity()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tracker.IsDivisible(gpu, 5, 2).Should().BeFalse();
|
||||
tracker.IsDivisible(gpu, 6, 2).Should().BeTrue();
|
||||
tracker.IsDivisible(gpu, 7, 2).Should().BeFalse();
|
||||
tracker.IsDivisibleGpu(gpu, 5, 2).Should().BeFalse();
|
||||
tracker.IsDivisibleGpu(gpu, 6, 2).Should().BeTrue();
|
||||
tracker.IsDivisibleGpu(gpu, 7, 2).Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -31,14 +41,26 @@ public class ModResidueTrackerTests
|
||||
[InlineData(5UL, 7UL, false)]
|
||||
[InlineData(11UL, 23UL, true)]
|
||||
[InlineData(11UL, 31UL, false)]
|
||||
public void IsDivisible_handles_mersenne_model(ulong p, ulong divisor, bool expected)
|
||||
public void IsDivisibleCpu_handles_mersenne_model(ulong p, ulong divisor, bool expected)
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Mersenne);
|
||||
tracker.IsDivisibleCpu(p, divisor).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(3UL, 7UL, true)]
|
||||
[InlineData(5UL, 31UL, true)]
|
||||
[InlineData(5UL, 7UL, false)]
|
||||
[InlineData(11UL, 23UL, true)]
|
||||
[InlineData(11UL, 31UL, false)]
|
||||
public void IsDivisibleGpu_handles_mersenne_model(ulong p, ulong divisor, bool expected)
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Mersenne);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
|
||||
try
|
||||
{
|
||||
tracker.IsDivisible(gpu, p, divisor).Should().Be(expected);
|
||||
tracker.IsDivisibleGpu(gpu, p, divisor).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -47,7 +69,31 @@ public class ModResidueTrackerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeOrAppend_tracks_existing_and_new_divisors()
|
||||
public void MergeOrAppendCpu_tracks_existing_and_new_divisors()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppendCpu(10, 3, out bool d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppendCpu(10, 5, out bool d5).Should().BeTrue();
|
||||
d5.Should().BeTrue();
|
||||
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppendCpu(10, 4, out bool d4).Should().BeTrue();
|
||||
d4.Should().BeFalse();
|
||||
|
||||
tracker.BeginMerge(11);
|
||||
tracker.MergeOrAppendCpu(11, 3, out d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppendCpu(11, 4, out d4).Should().BeTrue();
|
||||
d4.Should().BeFalse();
|
||||
tracker.MergeOrAppendCpu(11, 5, out d5).Should().BeTrue();
|
||||
d5.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeOrAppendGpu_tracks_existing_and_new_divisors()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
@@ -55,21 +101,21 @@ public class ModResidueTrackerTests
|
||||
try
|
||||
{
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppend(gpu, 10, 3, out bool d3).Should().BeTrue();
|
||||
tracker.MergeOrAppendGpu(gpu, 10, 3, out bool d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppend(gpu, 10, 5, out bool d5).Should().BeTrue();
|
||||
tracker.MergeOrAppendGpu(gpu, 10, 5, out bool d5).Should().BeTrue();
|
||||
d5.Should().BeTrue();
|
||||
|
||||
tracker.BeginMerge(10);
|
||||
tracker.MergeOrAppend(gpu, 10, 4, out bool d4).Should().BeTrue();
|
||||
tracker.MergeOrAppendGpu(gpu, 10, 4, out bool d4).Should().BeTrue();
|
||||
d4.Should().BeFalse();
|
||||
|
||||
tracker.BeginMerge(11);
|
||||
tracker.MergeOrAppend(gpu, 11, 3, out d3).Should().BeTrue();
|
||||
tracker.MergeOrAppendGpu(gpu, 11, 3, out d3).Should().BeTrue();
|
||||
d3.Should().BeFalse();
|
||||
tracker.MergeOrAppend(gpu, 11, 4, out d4).Should().BeTrue();
|
||||
tracker.MergeOrAppendGpu(gpu, 11, 4, out d4).Should().BeTrue();
|
||||
d4.Should().BeFalse();
|
||||
tracker.MergeOrAppend(gpu, 11, 5, out d5).Should().BeTrue();
|
||||
tracker.MergeOrAppendGpu(gpu, 11, 5, out d5).Should().BeTrue();
|
||||
d5.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
@@ -79,15 +125,26 @@ public class ModResidueTrackerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDivisible_requires_non_decreasing_numbers()
|
||||
public void IsDivisibleCpu_requires_non_decreasing_numbers()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
|
||||
tracker.IsDivisibleCpu(5, 2);
|
||||
|
||||
Action act = () => tracker.IsDivisibleCpu(4, 2);
|
||||
act.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDivisibleGpu_requires_non_decreasing_numbers()
|
||||
{
|
||||
var tracker = new ModResidueTracker(ResidueModel.Identity);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
tracker.IsDivisible(gpu, 5, 2);
|
||||
tracker.IsDivisibleGpu(gpu, 5, 2);
|
||||
|
||||
Action act = () => tracker.IsDivisible(gpu, 4, 2);
|
||||
Action act = () => tracker.IsDivisibleGpu(gpu, 4, 2);
|
||||
act.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -45,13 +45,12 @@ public class PrimeOrderCalculatorTests
|
||||
ulong expected = ComputeOrderByDoubling(prime);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
|
||||
ulong result = PrimeOrderCalculator.Calculate(
|
||||
ulong result = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
|
||||
@@ -66,21 +65,19 @@ public class PrimeOrderCalculatorTests
|
||||
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||
ulong heuristic = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder,
|
||||
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
ulong strict = PrimeOrderCalculator.Calculate(
|
||||
ulong strict = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault);
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
|
||||
@@ -94,13 +91,12 @@ public class PrimeOrderCalculatorTests
|
||||
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
ulong result = PrimeOrderCalculator.Calculate(
|
||||
ulong result = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
result.Should().Be(expectedOrder);
|
||||
@@ -117,13 +113,12 @@ public class PrimeOrderCalculatorTests
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
ulong result = PrimeOrderCalculator.Calculate(
|
||||
ulong result = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
prime: 13UL,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorDataPool.Shared.FromModulus(13UL),
|
||||
config,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
config);
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
result.Should().Be(ComputeOrderByDoubling(13UL));
|
||||
@@ -143,13 +138,12 @@ public class PrimeOrderCalculatorTests
|
||||
try
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(239UL);
|
||||
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||
ulong heuristic = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
heuristicConfig,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
heuristicConfig);
|
||||
|
||||
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
||||
|
||||
@@ -159,13 +153,12 @@ public class PrimeOrderCalculatorTests
|
||||
maxPowChecks: 1,
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
||||
|
||||
ulong strict = PrimeOrderCalculator.Calculate(
|
||||
ulong strict = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
strictConfig,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
strictConfig);
|
||||
|
||||
strict.Should().Be(heuristic);
|
||||
}
|
||||
@@ -182,12 +175,11 @@ public class PrimeOrderCalculatorTests
|
||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
UInt128 result = PrimeOrderCalculator.Calculate(
|
||||
UInt128 result = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
prime,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
result.Should().Be((UInt128)1229782938247309446UL);
|
||||
@@ -201,7 +193,7 @@ public class PrimeOrderCalculatorTests
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var mersenneDivisorCycles = MersenneDivisorCycles.GetCycle(gpu, prime).Should().Be((UInt128)1229782938247309446UL);
|
||||
var mersenneDivisorCycles = MersenneDivisorCycles.GetCycleGpu(gpu, prime).Should().Be((UInt128)1229782938247309446UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -51,14 +51,26 @@ public class UInt128ExtensionsTests
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
public void CalculateOrder_matches_naive(ulong q)
|
||||
public void CalculateOrderCpu_matches_naive(ulong q)
|
||||
{
|
||||
UInt128 q128 = q;
|
||||
ulong expected = NaiveOrder(q);
|
||||
q128.CalculateOrderCpu().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
public void CalculateOrderGpu_matches_naive(ulong q)
|
||||
{
|
||||
UInt128 q128 = q;
|
||||
ulong expected = NaiveOrder(q);
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
q128.CalculateOrder(gpu).Should().Be(expected);
|
||||
q128.CalculateOrderGpu(gpu).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -68,14 +68,27 @@ public class ULongExtensionsTests
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
[InlineData(11UL)]
|
||||
public void CalculateOrder_matches_naive_algorithm(ulong q)
|
||||
public void CalculateOrderCpu_matches_naive_algorithm(ulong q)
|
||||
{
|
||||
ulong expected = NaiveOrder(q);
|
||||
|
||||
q.CalculateOrderCpu().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(7UL)]
|
||||
[InlineData(11UL)]
|
||||
public void CalculateOrderGpu_matches_naive_algorithm(ulong q)
|
||||
{
|
||||
ulong expected = NaiveOrder(q);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
q.CalculateOrder(gpu).Should().Be(expected);
|
||||
q.CalculateOrderGpu(gpu).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -119,12 +132,22 @@ public class ULongExtensionsTests
|
||||
[InlineData(15UL, true)]
|
||||
[InlineData(7UL, false)]
|
||||
[InlineData(9UL, false)]
|
||||
public void SharesFactorWithExponentMinusOne_detects_relationship(ulong exponent, bool expected)
|
||||
public void SharesFactorWithExponentMinusOneCpu_detects_relationship(ulong exponent, bool expected)
|
||||
{
|
||||
exponent.SharesFactorWithExponentMinusOneCpu().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(15UL, true)]
|
||||
[InlineData(7UL, false)]
|
||||
[InlineData(9UL, false)]
|
||||
public void SharesFactorWithExponentMinusOneGpu_detects_relationship(ulong exponent, bool expected)
|
||||
{
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
exponent.SharesFactorWithExponentMinusOne(gpu).Should().Be(expected);
|
||||
exponent.SharesFactorWithExponentMinusOneGpu(gpu).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -213,15 +236,24 @@ public class ULongExtensionsTests
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(11UL, 13UL)]
|
||||
public void PowModCrt_matches_BigInteger(ulong exponent, ulong modulus)
|
||||
public void PowModCrtCpu_matches_BigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
|
||||
exponent.PowModCrtCpu((UInt128)modulus).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(11UL, 13UL)]
|
||||
public void PowModCrtGpu_matches_BigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
MersenneDivisorCycles cycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
exponent.PowModCrt(gpu, (UInt128)modulus, cycles).Should().Be(expected);
|
||||
exponent.PowModCrtGpu(gpu, (UInt128)modulus).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -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