Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef078df83d | ||
|
|
645969309c | ||
|
|
01ac80466b | ||
|
|
2b0d7278db | ||
|
|
1c0bd3f3d3 | ||
|
|
4ed9db7d0b | ||
|
|
99a7ec5b70 | ||
|
|
ca0cadc8a3 | ||
|
|
70eca5126b | ||
|
|
0be9596bd5 | ||
|
|
4f68c90d88 | ||
|
|
7aaccb5feb | ||
|
|
c334b9e878 | ||
|
|
e7ac18ea0b | ||
|
|
ef5a77cedd | ||
|
|
58c546cd87 | ||
|
|
8054f9fe59 | ||
|
|
b2ab47e0ee | ||
|
|
74e9e88977 | ||
|
|
f196f1fb96 | ||
|
|
a1a9c5f033 | ||
|
|
0bae2cb3e6 | ||
|
|
9e0c90a281 | ||
|
|
40bef43f0e | ||
|
|
ebfdc7d8b8 | ||
|
|
bcf50cf763 | ||
|
|
ce8c3a660a | ||
|
|
321157cf65 | ||
|
|
e1d4bbf12d | ||
|
|
77cc1fc280 | ||
|
|
668e161554 | ||
|
|
ce4c59beaa | ||
|
|
6bd8945565 | ||
|
|
99c9ddf73d | ||
|
|
32de7ebc75 | ||
|
|
fd0c978079 | ||
|
|
a19cbd245d |
No files matched your search
@@ -50,7 +50,7 @@ public class GpuPow2ModBenchmarks
|
|||||||
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, in GpuUInt128 modulus)
|
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
if (modulus.IsZero || modulus == GpuUInt128.One)
|
if (modulus.IsZero || modulus == GpuUInt128.One)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,14 +52,31 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark(Baseline = true)]
|
[Benchmark(Baseline = true)]
|
||||||
public ulong Baseline()
|
public ulong BaselineCpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
||||||
|
/// </summary>
|
||||||
|
[Benchmark]
|
||||||
|
public ulong BaselineGpu()
|
||||||
|
{
|
||||||
|
GetData(out var exponents, out var divisors, out _);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -69,14 +86,31 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BaselineWithKnownCycle()
|
public ulong BaselineWithKnownCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out var cycles);
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||||
|
/// </summary>
|
||||||
|
[Benchmark]
|
||||||
|
public ulong BaselineWithKnownCycleGpu()
|
||||||
|
{
|
||||||
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
|
|||||||
@@ -89,14 +89,32 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// large set.
|
/// large set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark(Baseline = true)]
|
// [Benchmark(Baseline = true)]
|
||||||
public ulong MontgomeryWithoutCycle()
|
public ulong MontgomeryWithoutCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
|
||||||
|
/// large set.
|
||||||
|
/// </summary>
|
||||||
|
// [Benchmark(Baseline = true)]
|
||||||
|
public ulong MontgomeryWithoutCycleGpu()
|
||||||
|
{
|
||||||
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -107,14 +125,32 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// on the large one (0.08×).
|
/// on the large one (0.08×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong MontgomeryWithPrecomputedCycle()
|
public ulong MontgomeryWithPrecomputedCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
|
||||||
|
/// on the large one (0.08×).
|
||||||
|
/// </summary>
|
||||||
|
[Benchmark]
|
||||||
|
public ulong MontgomeryWithPrecomputedCycleGpu()
|
||||||
|
{
|
||||||
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -125,7 +161,7 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark]
|
// [Benchmark]
|
||||||
public ulong MontgomeryWithGpuCycleComputation()
|
public ulong MontgomeryWithGpuCycleComputationCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -133,15 +169,33 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to
|
||||||
|
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
||||||
|
/// </summary>
|
||||||
|
// [Benchmark]
|
||||||
|
public ulong MontgomeryWithGpuCycleComputationGpu()
|
||||||
|
{
|
||||||
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong MontgomeryWithHeuristicCycleComputation()
|
public ulong MontgomeryWithHeuristicCycleComputationCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -149,7 +203,22 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ulong MontgomeryWithHeuristicCycleComputationGpu()
|
||||||
|
{
|
||||||
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -279,16 +348,17 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine("Trying heuristic. Prime order calculation");
|
Console.WriteLine("Trying heuristic. Prime order calculation");
|
||||||
#endif
|
#endif
|
||||||
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
ulong order = PrimeOrderCalculator.Calculate(
|
||||||
modulus,
|
modulus,
|
||||||
_previousPrimeOrder,
|
_previousPrimeOrder,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
if (orderResult.Order != 0UL)
|
if (order != 0UL)
|
||||||
{
|
{
|
||||||
_previousPrimeOrder = orderResult.Order;
|
_previousPrimeOrder = order;
|
||||||
return orderResult.Order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
_previousPrimeOrder = null;
|
_previousPrimeOrder = null;
|
||||||
@@ -307,15 +377,15 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
||||||
#endif
|
#endif
|
||||||
PrimeOrderCalculator.PrimeOrderResultWide orderResult = PrimeOrderCalculator.Calculate(
|
UInt128 order = PrimeOrderCalculator.Calculate(
|
||||||
modulus,
|
modulus,
|
||||||
_previousWidePrimeOrder,
|
_previousWidePrimeOrder,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
|
|
||||||
if (orderResult.Order != UInt128.Zero)
|
if (order != UInt128.Zero)
|
||||||
{
|
{
|
||||||
_previousWidePrimeOrder = orderResult.Order;
|
_previousWidePrimeOrder = order;
|
||||||
return orderResult.Order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
_previousWidePrimeOrder = null;
|
_previousWidePrimeOrder = null;
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return (UInt128)result;
|
return (UInt128)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldUseSingleBit(in GpuUInt128 exponent)
|
private static bool ShouldUseSingleBit(GpuUInt128 exponent)
|
||||||
{
|
{
|
||||||
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
||||||
{
|
{
|
||||||
@@ -257,7 +257,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||||
{
|
{
|
||||||
if (bitIndex >= 64)
|
if (bitIndex >= 64)
|
||||||
{
|
{
|
||||||
@@ -267,7 +267,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||||
{
|
{
|
||||||
if (windowStart != 0)
|
if (windowStart != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -359,10 +359,9 @@ public class PrimeOrderGpuHeuristicsTests
|
|||||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
result.Should().Be(3UL);
|
||||||
result.Order.Should().Be(3UL);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
ulong expected = ComputeOrderByDoubling(prime);
|
ulong expected = ComputeOrderByDoubling(prime);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
result.Should().Be(expected);
|
||||||
result.Order.Should().Be(expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@@ -62,21 +62,21 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder,
|
previousOrder,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
ulong strict = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
heuristic.Should().Be(strict);
|
||||||
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
|
||||||
heuristic.Order.Should().Be(strict.Order);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@@ -85,14 +85,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
[InlineData(3UL, 2UL)]
|
[InlineData(3UL, 2UL)]
|
||||||
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
||||||
{
|
{
|
||||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
result.Should().Be(expectedOrder);
|
||||||
result.Order.Should().Be(expectedOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -105,14 +105,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
maxPowChecks: 8,
|
maxPowChecks: 8,
|
||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
prime: 13UL,
|
prime: 13UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(13UL),
|
MontgomeryDivisorData.FromModulus(13UL),
|
||||||
config);
|
config,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
result.Should().Be(ComputeOrderByDoubling(13UL));
|
||||||
result.Order.Should().Be(ComputeOrderByDoubling(13UL));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -126,14 +126,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
||||||
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||||
prime: 239UL,
|
prime: 239UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
heuristicConfig);
|
heuristicConfig,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
||||||
heuristic.Order.Should().Be(ComputeOrderByDoubling(239UL));
|
|
||||||
|
|
||||||
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||||
smallFactorLimit: 2,
|
smallFactorLimit: 2,
|
||||||
@@ -141,14 +141,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
maxPowChecks: 1,
|
maxPowChecks: 1,
|
||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
ulong strict = PrimeOrderCalculator.Calculate(
|
||||||
prime: 239UL,
|
prime: 239UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
strictConfig);
|
strictConfig,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
strict.Should().Be(heuristic);
|
||||||
strict.Order.Should().Be(heuristic.Order);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -157,13 +157,12 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResultWide result = PrimeOrderCalculator.Calculate(
|
UInt128 result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
result.Should().Be((UInt128)1229782938247309446UL);
|
||||||
result.Order.Should().Be((UInt128)1229782938247309446UL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
internal static class DeterministicRandom
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ulong s_threadState;
|
||||||
|
|
||||||
|
private static long s_seedCounter;
|
||||||
|
|
||||||
|
public static ulong NextUInt64()
|
||||||
|
{
|
||||||
|
ulong state = s_threadState;
|
||||||
|
if (state == 0UL)
|
||||||
|
{
|
||||||
|
state = InitializeThreadState();
|
||||||
|
}
|
||||||
|
|
||||||
|
state ^= state >> 12;
|
||||||
|
state ^= state << 25;
|
||||||
|
state ^= state >> 27;
|
||||||
|
s_threadState = state;
|
||||||
|
return state * 2685821657736338717UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UInt128 NextUInt128()
|
||||||
|
{
|
||||||
|
UInt128 high = (UInt128)NextUInt64();
|
||||||
|
UInt128 low = (UInt128)NextUInt64();
|
||||||
|
return (high << 64) | low;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong InitializeThreadState()
|
||||||
|
{
|
||||||
|
ulong seed = (ulong)Interlocked.Increment(ref s_seedCounter);
|
||||||
|
seed += 0x9E3779B97F4A7C15UL;
|
||||||
|
seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL;
|
||||||
|
seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL;
|
||||||
|
seed ^= seed >> 31;
|
||||||
|
if (seed == 0UL)
|
||||||
|
{
|
||||||
|
seed = 0x9E3779B97F4A7C15UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,20 @@
|
|||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
internal struct ExponentRemainderStepper
|
internal struct ExponentRemainderStepper(in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
private readonly MontgomeryDivisorData _divisor;
|
private readonly MontgomeryDivisorData _divisor = divisor;
|
||||||
private readonly ulong _modulus;
|
private readonly ulong _modulus = divisor.Modulus;
|
||||||
private readonly ulong _nPrime;
|
private readonly ulong _nPrime = divisor.NPrime;
|
||||||
private readonly ulong _montgomeryOne;
|
private readonly ulong _montgomeryOne = divisor.MontgomeryOne;
|
||||||
private ulong _previousExponent;
|
private ulong _previousExponent = 0UL;
|
||||||
private ulong _currentMontgomery;
|
private ulong _currentMontgomery = divisor.MontgomeryOne;
|
||||||
private bool _hasState;
|
private bool _hasState = false;
|
||||||
|
|
||||||
public ExponentRemainderStepper(in MontgomeryDivisorData divisor)
|
public readonly bool IsValidModulus => _modulus > 1UL && (_modulus & 1UL) != 0UL;
|
||||||
{
|
|
||||||
_divisor = divisor;
|
|
||||||
_modulus = divisor.Modulus;
|
|
||||||
_nPrime = divisor.NPrime;
|
|
||||||
_montgomeryOne = divisor.MontgomeryOne;
|
|
||||||
_previousExponent = 0UL;
|
|
||||||
_currentMontgomery = divisor.MontgomeryOne;
|
|
||||||
_hasState = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsValidModulus => _modulus > 1UL && (_modulus & 1UL) != 0UL;
|
public readonly bool HasState => _hasState;
|
||||||
|
|
||||||
public bool HasState => _hasState;
|
public readonly ulong PreviousExponent => _previousExponent;
|
||||||
|
|
||||||
public ulong PreviousExponent => _previousExponent;
|
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
@@ -47,7 +36,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
||||||
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
|
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
|
||||||
// 2× gains for large exponents.
|
// 2× gains for large exponents.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
return ReduceCurrent();
|
return ReduceCurrent();
|
||||||
@@ -59,7 +48,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
||||||
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
||||||
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
|
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
|
||||||
ulong multiplier = delta.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
return ReduceCurrent();
|
return ReduceCurrent();
|
||||||
@@ -77,7 +66,7 @@ internal struct ExponentRemainderStepper
|
|||||||
{
|
{
|
||||||
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
||||||
// residue checks align with the optimized ProcessEightBitWindows timings.
|
// residue checks align with the optimized ProcessEightBitWindows timings.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
return _currentMontgomery == _montgomeryOne;
|
return _currentMontgomery == _montgomeryOne;
|
||||||
@@ -88,7 +77,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
||||||
// TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
// TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
||||||
// currently lags behind the benchmarked implementation for huge divisor cycles.
|
// currently lags behind the benchmarked implementation for huge divisor cycles.
|
||||||
ulong multiplier = delta.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
return _currentMontgomery == _montgomeryOne;
|
return _currentMontgomery == _montgomeryOne;
|
||||||
@@ -123,7 +112,7 @@ internal struct ExponentRemainderStepper
|
|||||||
{
|
{
|
||||||
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
||||||
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
|
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
isUnity = _currentMontgomery == _montgomeryOne;
|
isUnity = _currentMontgomery == _montgomeryOne;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
public readonly struct FactorEntry(ulong value, int exponent)
|
||||||
|
{
|
||||||
|
public readonly ulong Value = value;
|
||||||
|
public readonly int Exponent = exponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -267,7 +267,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Add(in GpuUInt128 other) => AddInternal(other.High, other.Low);
|
public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void AddInternal(ulong otherHigh, ulong otherLow)
|
private void AddInternal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -294,17 +294,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
public void AddMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
public void AddMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
public void AddMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
@@ -390,7 +390,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
public void AddMod(GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(ulong value, ulong modulus)
|
public void AddMod(ulong value, ulong modulus)
|
||||||
@@ -414,7 +414,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Sub(in GpuUInt128 other) => SubInternal(other.High, other.Low);
|
public void Sub(GpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void SubInternal(ulong otherHigh, ulong otherLow)
|
private void SubInternal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -461,7 +461,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Mul64(in GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
public void Mul64(GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -588,7 +588,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int TrailingZeroCount(in GpuUInt128 value)
|
public static int TrailingZeroCount(GpuUInt128 value)
|
||||||
{
|
{
|
||||||
if (value.Low != 0UL)
|
if (value.Low != 0UL)
|
||||||
{
|
{
|
||||||
@@ -646,17 +646,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
public void SubMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
public void SubMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
public void SubMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
@@ -727,7 +727,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Mul(in GpuUInt128 other)
|
public void Mul(GpuUInt128 other)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
||||||
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
hits[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
hits[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
@@ -770,7 +770,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
|
|
||||||
results[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: true);
|
results[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
|
|||||||
@@ -415,9 +415,9 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig config,
|
PrimeOrderCalculator.PrimeOrderSearchConfig config,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
out PrimeOrderCalculator.PrimeOrderResult result)
|
out ulong order)
|
||||||
{
|
{
|
||||||
result = default;
|
order = 0UL;
|
||||||
|
|
||||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||||
var execution = lease.EnterExecutionScope();
|
var execution = lease.EnterExecutionScope();
|
||||||
@@ -497,18 +497,17 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong order = 0UL;
|
|
||||||
resultBuffer.View.CopyToCPU(ref order, 1);
|
resultBuffer.View.CopyToCPU(ref order, 1);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderStatus finalStatus = kernelStatus == PrimeOrderKernelStatus.Found
|
if (kernelStatus == PrimeOrderKernelStatus.FactoringFailure)
|
||||||
? PrimeOrderCalculator.PrimeOrderStatus.Found
|
{
|
||||||
: PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved;
|
order = 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
result = new PrimeOrderCalculator.PrimeOrderResult(finalStatus, order);
|
|
||||||
DisposeResources();
|
DisposeResources();
|
||||||
lease.Dispose();
|
lease.Dispose();
|
||||||
|
|
||||||
return true;
|
return order != 0UL;
|
||||||
|
|
||||||
void DisposeResources()
|
void DisposeResources()
|
||||||
{
|
{
|
||||||
@@ -812,7 +811,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong reduced = phi / factor;
|
ulong reduced = phi / factor;
|
||||||
if (reduced.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL)
|
if (reduced.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1596,7 +1595,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
|
|
||||||
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong CalculateByDoublingKernel(ulong prime)
|
private static ulong CalculateByDoublingKernel(ulong prime)
|
||||||
@@ -1796,7 +1795,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false);
|
return exponent.Pow2MontgomeryModWindowedGpu(divisorData, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
||||||
@@ -1812,7 +1811,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
|
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
|
||||||
{
|
{
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
remainders[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false);
|
remainders[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
internal static class HeuristicFailureLog
|
||||||
|
{
|
||||||
|
private const string LogFileName = "prime-order-heuristic-fallbacks.log";
|
||||||
|
private const int FlushThreshold = 4096;
|
||||||
|
private const int MaxBufferedEntries = 16;
|
||||||
|
private static readonly ThreadLocal<StringBuilder?> s_threadBuilder = new(() => StringBuilderPool.Rent(), trackAllValues: true);
|
||||||
|
private static readonly ThreadLocal<int> s_threadEntryCount = new(() => 0, trackAllValues: true);
|
||||||
|
private static readonly object s_fileLock = new();
|
||||||
|
private static readonly string s_logPath = Path.Combine(AppContext.BaseDirectory, LogFileName);
|
||||||
|
private static readonly UTF8Encoding s_utf8NoBom = new(false);
|
||||||
|
|
||||||
|
static HeuristicFailureLog()
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.ProcessExit += static (_, _) => FlushAll(force: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Record(ulong prime, ulong? candidateOrder, HeuristicFailureReason reason)
|
||||||
|
{
|
||||||
|
string primeText = prime.ToString(CultureInfo.InvariantCulture);
|
||||||
|
string? candidateText = candidateOrder.HasValue ? candidateOrder.Value.ToString(CultureInfo.InvariantCulture) : null;
|
||||||
|
RecordInternal(primeText, candidateText, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Record(UInt128 prime, UInt128? candidateOrder, HeuristicFailureReason reason)
|
||||||
|
{
|
||||||
|
string primeText = prime.ToString(CultureInfo.InvariantCulture);
|
||||||
|
string? candidateText = candidateOrder.HasValue ? candidateOrder.Value.ToString(CultureInfo.InvariantCulture) : null;
|
||||||
|
RecordInternal(primeText, candidateText, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RecordInternal(string primeText, string? candidateText, HeuristicFailureReason reason)
|
||||||
|
{
|
||||||
|
StringBuilder? existing = s_threadBuilder.Value;
|
||||||
|
StringBuilder builder = existing ?? StringBuilderPool.Rent();
|
||||||
|
int count = s_threadEntryCount.Value + 1;
|
||||||
|
bool forceFlush = count >= MaxBufferedEntries;
|
||||||
|
builder.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
|
||||||
|
builder.Append(" | prime=");
|
||||||
|
builder.Append(primeText);
|
||||||
|
builder.Append(" | reason=");
|
||||||
|
builder.Append(reason);
|
||||||
|
if (candidateText is not null)
|
||||||
|
{
|
||||||
|
builder.Append(" | candidateOrder=");
|
||||||
|
builder.Append(candidateText);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine();
|
||||||
|
FlushBuilderIfNeeded(builder, forceFlush);
|
||||||
|
if (builder.Length == 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_threadEntryCount.Value = count;
|
||||||
|
s_threadBuilder.Value = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FlushAll(bool force)
|
||||||
|
{
|
||||||
|
foreach (StringBuilder? builder in s_threadBuilder.Values)
|
||||||
|
{
|
||||||
|
if (builder is not null)
|
||||||
|
{
|
||||||
|
FlushBuilderIfNeeded(builder, force);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FlushBuilderIfNeeded(StringBuilder builder, bool force)
|
||||||
|
{
|
||||||
|
if (builder.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!force && builder.Length < FlushThreshold)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string text = builder.ToString();
|
||||||
|
builder.Clear();
|
||||||
|
lock (s_fileLock)
|
||||||
|
{
|
||||||
|
using FileStream stream = new FileStream(s_logPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||||
|
using StreamWriter writer = new StreamWriter(stream, s_utf8NoBom);
|
||||||
|
writer.Write(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
internal enum HeuristicFailureReason
|
||||||
|
{
|
||||||
|
PhiPartialFactorizationFailed,
|
||||||
|
StrictModeRequested,
|
||||||
|
HeuristicPipelineUnresolved,
|
||||||
|
HeuristicPipelineAssumedFullOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
@@ -54,15 +51,15 @@ public class MersenneDivisorCycles
|
|||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CycleEqualsExponent(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
public static bool CycleEqualsExponentCpu(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||||
{
|
{
|
||||||
Dictionary<ulong, FactorCacheEntry>? cache = null;
|
Dictionary<ulong, FactorCacheEntry>? cache = null;
|
||||||
return CycleEqualsExponent(divisor, exponent, divisorData, ref cache);
|
return CycleEqualsExponentCpu(divisor, exponent, divisorData, ref cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CycleEqualsExponent(ulong divisor, ulong exponent, in MontgomeryDivisorData divisorData, ref Dictionary<ulong, FactorCacheEntry>? factorCache)
|
public static bool CycleEqualsExponentCpu(ulong divisor, ulong exponent, in MontgomeryDivisorData divisorData, ref Dictionary<ulong, FactorCacheEntry>? factorCache)
|
||||||
{
|
{
|
||||||
if (CycleEqualsExponentForMersenneCandidate(divisor, divisorData, exponent))
|
if (CycleEqualsExponentForMersenneCandidateCpu(divisor, divisorData, exponent))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -97,15 +94,15 @@ public class MersenneDivisorCycles
|
|||||||
factorCache = cache;
|
factorCache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryCalculateCycleLengthForExponent(divisor, exponent, divisorData, cache, out ulong cycleLength) && cycleLength != 0UL)
|
if (TryCalculateCycleLengthForExponentCpu(divisor, exponent, divisorData, cache, out ulong cycleLength) && cycleLength != 0UL)
|
||||||
{
|
{
|
||||||
return cycleLength == exponent;
|
return cycleLength == exponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
public static bool CycleEqualsExponentForMersenneCandidateCpu(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||||
{
|
{
|
||||||
if (!IsValidMersenneDivisorCandidate(divisor, exponent))
|
if (!IsValidMersenneDivisorCandidate(divisor, exponent))
|
||||||
{
|
{
|
||||||
@@ -126,7 +123,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
||||||
@@ -234,14 +231,14 @@ public class MersenneDivisorCycles
|
|||||||
return (UInt128)cycle;
|
return (UInt128)cycle;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResultWide wideResult = PrimeOrderCalculator.Calculate(
|
UInt128 wideOrder = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (wideResult.Order != UInt128.Zero)
|
if (wideOrder != UInt128.Zero)
|
||||||
{
|
{
|
||||||
return wideResult.Order;
|
return wideOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, find order of 2 mod divisor
|
// Otherwise, find order of 2 mod divisor
|
||||||
@@ -264,7 +261,7 @@ public class MersenneDivisorCycles
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryCalculateCycleLengthForExponent(
|
public static bool TryCalculateCycleLengthForExponentCpu(
|
||||||
ulong divisor,
|
ulong divisor,
|
||||||
ulong exponent,
|
ulong exponent,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
@@ -285,15 +282,15 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
ulong order = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (orderResult.Order != 0UL)
|
if (order != 0UL)
|
||||||
{
|
{
|
||||||
cycleLength = orderResult.Order;
|
cycleLength = order;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +324,7 @@ public class MersenneDivisorCycles
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
cycleLength = ReduceOrderCpu(divisorData, phi, factorCounts);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,7 +468,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
|
private static ulong ReduceOrderCpu(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
|
||||||
{
|
{
|
||||||
if (factorCounts.Count == 0)
|
if (factorCounts.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -505,7 +502,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong candidate = order / prime;
|
ulong candidate = order / prime;
|
||||||
if (candidate.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL)
|
if (candidate.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL)
|
||||||
{
|
{
|
||||||
order = candidate;
|
order = candidate;
|
||||||
continue;
|
continue;
|
||||||
@@ -530,20 +527,31 @@ public class MersenneDivisorCycles
|
|||||||
return 2UL;
|
return 2UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PollardRhoMontgomeryReducer reducer = PollardRhoMontgomeryReducer.Create(n);
|
||||||
|
ulong diff = 0UL;
|
||||||
|
ulong d = 0UL;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
ulong c = (NextRandomUInt64() % (n - 1UL)) + 1UL;
|
ulong c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
|
||||||
ulong x = (NextRandomUInt64() % (n - 2UL)) + 2UL;
|
ulong xStandard = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
|
||||||
ulong y = x;
|
ulong yStandard = xStandard;
|
||||||
ulong d = 1UL;
|
ulong cMontgomery = reducer.ConvertToMontgomery(c);
|
||||||
|
ulong xMontgomery = reducer.ConvertToMontgomery(xStandard);
|
||||||
|
ulong yMontgomery = xMontgomery;
|
||||||
|
d = 1UL;
|
||||||
|
|
||||||
while (d == 1UL)
|
while (d == 1UL)
|
||||||
{
|
{
|
||||||
x = AdvancePolynomial(x, c, n);
|
xMontgomery = reducer.Advance(xMontgomery, cMontgomery);
|
||||||
y = AdvancePolynomial(y, c, n);
|
xStandard = reducer.AdvanceStandard(xStandard, c);
|
||||||
y = AdvancePolynomial(y, c, n);
|
|
||||||
|
|
||||||
ulong diff = x > y ? x - y : y - x;
|
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
|
||||||
|
yStandard = reducer.AdvanceStandard(yStandard, c);
|
||||||
|
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
|
||||||
|
yStandard = reducer.AdvanceStandard(yStandard, c);
|
||||||
|
|
||||||
|
diff = xStandard > yStandard ? xStandard - yStandard : yStandard - xStandard;
|
||||||
d = BinaryGcd(diff, n);
|
d = BinaryGcd(diff, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,13 +562,6 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong AdvancePolynomial(ulong x, ulong c, ulong modulus)
|
|
||||||
{
|
|
||||||
UInt128 value = (UInt128)x * x + c;
|
|
||||||
return (ulong)(value % modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong BinaryGcd(ulong a, ulong b)
|
private static ulong BinaryGcd(ulong a, ulong b)
|
||||||
{
|
{
|
||||||
if (a == 0UL)
|
if (a == 0UL)
|
||||||
@@ -592,13 +593,6 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong NextRandomUInt64()
|
|
||||||
{
|
|
||||||
Span<byte> buffer = stackalloc byte[8];
|
|
||||||
RandomNumberGenerator.Fill(buffer);
|
|
||||||
return BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct FactorCacheEntry
|
public readonly struct FactorCacheEntry
|
||||||
{
|
{
|
||||||
public FactorCacheEntry(ulong[] primes, byte[] exponents, int count)
|
public FactorCacheEntry(ulong[] primes, byte[] exponents, int count)
|
||||||
@@ -940,15 +934,15 @@ public class MersenneDivisorCycles
|
|||||||
|
|
||||||
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
||||||
{
|
{
|
||||||
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
ulong order = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (orderResult.Order != 0UL)
|
if (order != 0UL)
|
||||||
{
|
{
|
||||||
cycleLength = orderResult.Order;
|
cycleLength = order;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = threadCount;
|
|
||||||
|
|
||||||
candidates.Sort();
|
candidates.Sort();
|
||||||
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
||||||
int candidateCount = candidateSpan.Length;
|
int candidateCount = candidateSpan.Length;
|
||||||
@@ -38,6 +36,9 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
if (previousResults is not null && previousResults.Count > 0)
|
if (previousResults is not null && previousResults.Count > 0)
|
||||||
{
|
{
|
||||||
int recordedCount = previousResults.Count;
|
int recordedCount = previousResults.Count;
|
||||||
|
bool preferSortedMerge = recordedCount <= 4096 || (recordedCount * 4L) <= candidateCount;
|
||||||
|
if (preferSortedMerge)
|
||||||
|
{
|
||||||
ulong[] recordedCandidates = ArrayPool<ulong>.Shared.Rent(recordedCount);
|
ulong[] recordedCandidates = ArrayPool<ulong>.Shared.Rent(recordedCount);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -75,7 +76,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
if (writeIndex < candidateCount)
|
if (writeIndex < candidateCount)
|
||||||
{
|
{
|
||||||
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
|
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
|
||||||
candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
candidateSpan = CollectionsMarshal.AsSpan(candidates); // candidateSpan now tracks the reduced list.
|
||||||
candidateCount = candidateSpan.Length;
|
candidateCount = candidateSpan.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,6 +85,30 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
ArrayPool<ulong>.Shared.Return(recordedCandidates, clearArray: true);
|
ArrayPool<ulong>.Shared.Return(recordedCandidates, clearArray: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int writeIndex = 0;
|
||||||
|
for (int readIndex = 0; readIndex < candidateCount; readIndex++)
|
||||||
|
{
|
||||||
|
ulong candidate = candidateSpan[readIndex];
|
||||||
|
|
||||||
|
if (previousResults.ContainsKey(candidate))
|
||||||
|
{
|
||||||
|
skippedByPreviousResults++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidateSpan[writeIndex++] = candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writeIndex < candidateCount)
|
||||||
|
{
|
||||||
|
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
|
||||||
|
candidateSpan = CollectionsMarshal.AsSpan(candidates); // candidateSpan now tracks the reduced list.
|
||||||
|
candidateCount = candidateSpan.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (skippedByPreviousResults > 0)
|
if (skippedByPreviousResults > 0)
|
||||||
{
|
{
|
||||||
@@ -263,7 +288,32 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
MaxDegreeOfParallelism = workerCount
|
MaxDegreeOfParallelism = workerCount
|
||||||
};
|
};
|
||||||
|
|
||||||
Parallel.ForEach(filteredPrimes, options, ProcessPrime);
|
primeCount = filteredPrimes.Count; // Reusing primeCount to track the filtered count.
|
||||||
|
int primesPerWorker = (primeCount + workerCount - 1) / workerCount;
|
||||||
|
|
||||||
|
Parallel.For(
|
||||||
|
0,
|
||||||
|
workerCount,
|
||||||
|
options,
|
||||||
|
workerIndex =>
|
||||||
|
{
|
||||||
|
int start = workerIndex * primesPerWorker;
|
||||||
|
if (start >= primeCount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = start + primesPerWorker;
|
||||||
|
if (end > primeCount)
|
||||||
|
{
|
||||||
|
end = primeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = start; i < end; i++)
|
||||||
|
{
|
||||||
|
ProcessPrime(filteredPrimes[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,10 +371,6 @@ public static class MersennePrimeFactorTester
|
|||||||
return 2UL;
|
return 2UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rng = new Random(1234567); // TODO: Replace this RNG with the deterministic span-based
|
|
||||||
// sequence measured faster in PollardRhoBenchmarks so we avoid
|
|
||||||
// per-call Random allocations.
|
|
||||||
|
|
||||||
ulong c, d, x, y, diff;
|
ulong c, d, x, y, diff;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -383,8 +379,8 @@ public static class MersennePrimeFactorTester
|
|||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (ulong)rng.Next(2, int.MaxValue);
|
c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
|
||||||
x = (ulong)rng.Next(2, int.MaxValue);
|
x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
|
||||||
y = x;
|
y = x;
|
||||||
d = 1UL;
|
d = 1UL;
|
||||||
|
|
||||||
@@ -523,9 +519,6 @@ public static class MersennePrimeFactorTester
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rng = new Random(1234567); // TODO: Replace with the deterministic UInt128 sequence from
|
|
||||||
// PollardRho128Benchmarks so we eliminate Random allocations.
|
|
||||||
|
|
||||||
UInt128 c, d, x, y, diff;
|
UInt128 c, d, x, y, diff;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -534,8 +527,8 @@ public static class MersennePrimeFactorTester
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (UInt128)rng.NextInt64(2, long.MaxValue);
|
c = (DeterministicRandom.NextUInt128() % (n - 1)) + 1;
|
||||||
x = (UInt128)rng.NextInt64(2, long.MaxValue);
|
x = (DeterministicRandom.NextUInt128() % (n - 2)) + 2;
|
||||||
y = x;
|
y = x;
|
||||||
d = 1;
|
d = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
public sealed class PartialFactorResult
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static PartialFactorResult? s_poolHead;
|
||||||
|
|
||||||
|
public FactorEntry[]? Factors;
|
||||||
|
private PartialFactorResult? _next;
|
||||||
|
public ulong Cofactor;
|
||||||
|
public bool FullyFactored;
|
||||||
|
public int Count;
|
||||||
|
|
||||||
|
private PartialFactorResult()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartialFactorResult Rent(in FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count)
|
||||||
|
{
|
||||||
|
PartialFactorResult? instance = s_poolHead;
|
||||||
|
if (instance is null)
|
||||||
|
{
|
||||||
|
instance = new();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_poolHead = instance._next;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance._next = null;
|
||||||
|
instance.Factors = factors;
|
||||||
|
instance.Cofactor = cofactor;
|
||||||
|
instance.FullyFactored = fullyFactored;
|
||||||
|
instance.Count = count;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly PartialFactorResult Empty = new() { Cofactor = 1UL, FullyFactored = true };
|
||||||
|
|
||||||
|
public PartialFactorResult WithAdditionalPrime(ulong prime)
|
||||||
|
{
|
||||||
|
FactorEntry[]? source = Factors;
|
||||||
|
if (source is null || Count == 0)
|
||||||
|
{
|
||||||
|
FactorEntry[] local = ArrayPool<FactorEntry>.Shared.Rent(1);
|
||||||
|
local[0] = new FactorEntry(prime, 1);
|
||||||
|
return Rent(local, 1UL, true, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
FactorEntry[] extended = ArrayPool<FactorEntry>.Shared.Rent(Count + 1);
|
||||||
|
Array.Copy(source, 0, extended, 0, Count);
|
||||||
|
extended[Count] = new FactorEntry(prime, 1);
|
||||||
|
Span<FactorEntry> span = extended.AsSpan(0, Count + 1);
|
||||||
|
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
|
return Rent(extended, 1UL, true, Count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this != Empty)
|
||||||
|
{
|
||||||
|
FactorEntry[]? factors = Factors;
|
||||||
|
if (factors is not null)
|
||||||
|
{
|
||||||
|
Factors = null;
|
||||||
|
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = s_poolHead;
|
||||||
|
s_poolHead = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ public static class PerfectNumberConstants
|
|||||||
public const int MaxQForDivisorCycles = 4_000_000;
|
public const int MaxQForDivisorCycles = 4_000_000;
|
||||||
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
||||||
public const int MaxOddPowersCount = 128;
|
public const int MaxOddPowersCount = 128;
|
||||||
|
public const int PooledArrayThreshold = 64;
|
||||||
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
||||||
// divisor-cycle datasets we generate offline.
|
// divisor-cycle datasets we generate offline.
|
||||||
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
internal readonly struct PollardRhoMontgomeryReducer
|
||||||
|
{
|
||||||
|
private readonly MontgomeryDivisorData _divisorData;
|
||||||
|
private readonly ulong _reciprocal;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static bool s_hasCachedReciprocal;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ulong s_cachedReciprocal;
|
||||||
|
|
||||||
|
public PollardRhoMontgomeryReducer(in MontgomeryDivisorData divisorData, ulong reciprocal)
|
||||||
|
{
|
||||||
|
_divisorData = divisorData;
|
||||||
|
_reciprocal = reciprocal == 0UL || reciprocal == ulong.MaxValue
|
||||||
|
? ReciprocalMath.ComputeReciprocalEstimate(divisorData.Modulus)
|
||||||
|
: reciprocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Modulus => _divisorData.Modulus;
|
||||||
|
private ulong NPrime => _divisorData.NPrime;
|
||||||
|
private ulong MontgomeryTwoSquared => _divisorData.MontgomeryTwoSquared;
|
||||||
|
private ulong MontgomeryOne => _divisorData.MontgomeryOne;
|
||||||
|
private ulong MontgomeryTwo => _divisorData.MontgomeryTwo;
|
||||||
|
public ulong Reciprocal => _reciprocal;
|
||||||
|
|
||||||
|
public static PollardRhoMontgomeryReducer Create(ulong modulus)
|
||||||
|
{
|
||||||
|
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(modulus);
|
||||||
|
ulong reciprocal;
|
||||||
|
if (s_hasCachedReciprocal)
|
||||||
|
{
|
||||||
|
reciprocal = ReciprocalMath.RefineReciprocalEstimate(s_cachedReciprocal, modulus);
|
||||||
|
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(modulus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_cachedReciprocal = reciprocal;
|
||||||
|
s_hasCachedReciprocal = true;
|
||||||
|
return new PollardRhoMontgomeryReducer(divisorData, reciprocal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PollardRhoMontgomeryReducer Create(in MontgomeryDivisorData divisorData, ulong previousReciprocal)
|
||||||
|
{
|
||||||
|
ulong reciprocal = ReciprocalMath.RefineReciprocalEstimate(previousReciprocal, divisorData.Modulus);
|
||||||
|
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(divisorData.Modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_cachedReciprocal = reciprocal;
|
||||||
|
s_hasCachedReciprocal = true;
|
||||||
|
return new PollardRhoMontgomeryReducer(divisorData, reciprocal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ulong ConvertToMontgomery(ulong value)
|
||||||
|
{
|
||||||
|
if (value == 0UL)
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == 1UL)
|
||||||
|
{
|
||||||
|
return MontgomeryOne;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == 2UL)
|
||||||
|
{
|
||||||
|
return MontgomeryTwo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.MontgomeryMultiply(MontgomeryTwoSquared, Modulus, NPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ulong ReduceFromMontgomery(ulong montgomeryValue)
|
||||||
|
{
|
||||||
|
return montgomeryValue.MontgomeryMultiply(1UL, Modulus, NPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ulong Advance(ulong montgomeryValue, ulong cMontgomery)
|
||||||
|
{
|
||||||
|
ulong squared = montgomeryValue.MontgomeryMultiply(montgomeryValue, Modulus, NPrime);
|
||||||
|
return MontgomeryAdd(squared, cMontgomery, Modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ulong AdvanceStandard(ulong standardValue, ulong c)
|
||||||
|
{
|
||||||
|
UInt128 polynomial = (UInt128)standardValue * standardValue + c;
|
||||||
|
return ReciprocalMath.Reduce(polynomial, Modulus, _reciprocal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong MontgomeryAdd(ulong a, ulong b, ulong modulus)
|
||||||
|
{
|
||||||
|
UInt128 total = (UInt128)a + b;
|
||||||
|
if (total >= modulus)
|
||||||
|
{
|
||||||
|
total -= modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ulong)total;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,30 +9,18 @@ namespace PerfectNumbers.Core;
|
|||||||
|
|
||||||
internal static partial class PrimeOrderCalculator
|
internal static partial class PrimeOrderCalculator
|
||||||
{
|
{
|
||||||
internal readonly struct PrimeOrderResultWide
|
public static UInt128 Calculate(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
|
||||||
{
|
|
||||||
public PrimeOrderResultWide(PrimeOrderStatus status, UInt128 order)
|
|
||||||
{
|
|
||||||
Status = status;
|
|
||||||
Order = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrimeOrderStatus Status { get; }
|
|
||||||
|
|
||||||
public UInt128 Order { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrimeOrderResultWide Calculate(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
|
|
||||||
=> Calculate(prime, previousOrder, config, PrimeOrderHeuristicDevice.Gpu);
|
=> Calculate(prime, previousOrder, config, PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
public static PrimeOrderResultWide Calculate(
|
public static UInt128 Calculate(
|
||||||
UInt128 prime,
|
UInt128 prime,
|
||||||
UInt128? previousOrder,
|
UInt128? previousOrder,
|
||||||
PrimeOrderSearchConfig config,
|
PrimeOrderSearchConfig config,
|
||||||
PrimeOrderHeuristicDevice device)
|
PrimeOrderHeuristicDevice device)
|
||||||
{
|
{
|
||||||
using var scope = UsePow2Mode(device);
|
var scope = UsePow2Mode(device);
|
||||||
MontgomeryDivisorData divisorData;
|
MontgomeryDivisorData divisorData;
|
||||||
|
UInt128 result;
|
||||||
if (prime <= ulong.MaxValue)
|
if (prime <= ulong.MaxValue)
|
||||||
{
|
{
|
||||||
ulong? previous = null;
|
ulong? previous = null;
|
||||||
@@ -51,41 +39,49 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
ulong prime64 = (ulong)prime;
|
ulong prime64 = (ulong)prime;
|
||||||
divisorData = MontgomeryDivisorData.FromModulus(prime64);
|
divisorData = MontgomeryDivisorData.FromModulus(prime64);
|
||||||
PrimeOrderResult result = Calculate(prime64, previous, divisorData, config, device);
|
ulong order64 = Calculate(prime64, previous, divisorData, config, device);
|
||||||
return new PrimeOrderResultWide(result.Status, result.Order);
|
result = order64 == 0UL ? UInt128.Zero : (UInt128)order64;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
divisorData = default;
|
divisorData = default;
|
||||||
|
result = CalculateWideInternal(prime, previousOrder, divisorData, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CalculateWideInternal(prime, previousOrder, divisorData, config);
|
scope.Dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResultWide CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
private static UInt128 CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (prime <= UInt128.One)
|
if (prime <= UInt128.One)
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, UInt128.One);
|
return UInt128.One;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prime == (UInt128)3UL)
|
if (prime == (UInt128)3UL)
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, (UInt128)2UL);
|
return (UInt128)2UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 phi = prime - UInt128.One;
|
UInt128 phi = prime - UInt128.One;
|
||||||
using var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
||||||
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
||||||
|
UInt128 result;
|
||||||
if (phiFactors.Factors is null)
|
if (phiFactors.Factors is null)
|
||||||
{
|
{
|
||||||
return FinishStrictlyWide(prime, divisorData, config.Mode);
|
result = FinishStrictlyWide(prime, divisorData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
|
debugScope.Dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResultWide RunHeuristicPipelineWide(
|
private static UInt128 RunHeuristicPipelineWide(
|
||||||
UInt128 prime,
|
UInt128 prime,
|
||||||
UInt128? previousOrder,
|
UInt128? previousOrder,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
@@ -95,7 +91,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
|
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, phi);
|
return phi;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
|
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
|
||||||
@@ -103,27 +99,25 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
|
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, candidateOrder);
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.Mode == PrimeOrderMode.Strict)
|
if (config.Mode == PrimeOrderMode.Strict)
|
||||||
{
|
{
|
||||||
return FinishStrictlyWide(prime, divisorData, PrimeOrderMode.Strict);
|
return FinishStrictlyWide(prime, divisorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
|
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, order);
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FinishStrictlyWide(prime, divisorData, config.Mode);
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResultWide FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData, PrimeOrderMode mode)
|
private static UInt128 FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
UInt128 strictOrder = CalculateByFactorizationWide(prime, divisorData);
|
return CalculateByFactorizationWide(prime, divisorData);
|
||||||
PrimeOrderStatus status = mode == PrimeOrderMode.Strict ? PrimeOrderStatus.Found : PrimeOrderStatus.HeuristicUnresolved;
|
|
||||||
return new PrimeOrderResultWide(status, strictOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
||||||
|
|||||||
@@ -1,65 +1,30 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
internal static partial class PrimeOrderCalculator
|
internal static partial class PrimeOrderCalculator
|
||||||
{
|
{
|
||||||
internal enum PrimeOrderStatus
|
|
||||||
{
|
|
||||||
Found,
|
|
||||||
HeuristicUnresolved,
|
|
||||||
}
|
|
||||||
|
|
||||||
internal readonly struct PrimeOrderResult
|
|
||||||
{
|
|
||||||
public PrimeOrderResult(PrimeOrderStatus status, ulong order)
|
|
||||||
{
|
|
||||||
Status = status;
|
|
||||||
Order = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrimeOrderStatus Status { get; }
|
|
||||||
|
|
||||||
public ulong Order { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum PrimeOrderMode
|
internal enum PrimeOrderMode
|
||||||
{
|
{
|
||||||
Heuristic,
|
Heuristic,
|
||||||
Strict,
|
Strict,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal readonly struct PrimeOrderSearchConfig
|
internal readonly struct PrimeOrderSearchConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, PrimeOrderMode mode)
|
||||||
{
|
{
|
||||||
public PrimeOrderSearchConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, PrimeOrderMode mode)
|
public static PrimeOrderSearchConfig HeuristicDefault => new(smallFactorLimit: 100_000, pollardRhoMilliseconds: 128, maxPowChecks: 24, PrimeOrderMode.Heuristic);
|
||||||
{
|
public static PrimeOrderSearchConfig StrictDefault => new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, PrimeOrderMode.Strict);
|
||||||
SmallFactorLimit = smallFactorLimit;
|
|
||||||
PollardRhoMilliseconds = pollardRhoMilliseconds;
|
|
||||||
MaxPowChecks = maxPowChecks;
|
|
||||||
Mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public uint SmallFactorLimit { get; }
|
public readonly uint SmallFactorLimit = smallFactorLimit;
|
||||||
|
public readonly int PollardRhoMilliseconds = pollardRhoMilliseconds;
|
||||||
public int PollardRhoMilliseconds { get; }
|
public readonly int MaxPowChecks = maxPowChecks;
|
||||||
|
public readonly PrimeOrderMode Mode = mode;
|
||||||
public int MaxPowChecks { get; }
|
|
||||||
|
|
||||||
public PrimeOrderMode Mode { get; }
|
|
||||||
|
|
||||||
public static PrimeOrderSearchConfig HeuristicDefault => new(100_000, 128, 24, PrimeOrderMode.Heuristic);
|
|
||||||
|
|
||||||
public static PrimeOrderSearchConfig StrictDefault => new(1_000_000, 0, 0, PrimeOrderMode.Strict);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum PrimeOrderHeuristicDevice
|
internal enum PrimeOrderHeuristicDevice
|
||||||
@@ -83,8 +48,18 @@ internal static partial class PrimeOrderCalculator
|
|||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static bool s_debugLoggingEnabled;
|
private static bool s_debugLoggingEnabled;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<ulong>? s_partialFactorPending;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Stack<ulong>? s_partialFactorCompositeStack;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, int>? s_partialFactorCounts;
|
||||||
|
|
||||||
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
|
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
|
||||||
private readonly struct Pow2ModeScope : IDisposable
|
|
||||||
|
private readonly struct Pow2ModeScope
|
||||||
{
|
{
|
||||||
private readonly bool _previousInitialized;
|
private readonly bool _previousInitialized;
|
||||||
private readonly bool _previousAllow;
|
private readonly bool _previousAllow;
|
||||||
@@ -108,7 +83,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct DebugLoggingScope : IDisposable
|
private readonly struct DebugLoggingScope
|
||||||
{
|
{
|
||||||
private readonly bool _previous;
|
private readonly bool _previous;
|
||||||
|
|
||||||
@@ -146,93 +121,95 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PrimeOrderResult Calculate(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
public static ulong Calculate(
|
||||||
=> Calculate(prime, previousOrder, divisorData, config, PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
public static PrimeOrderResult Calculate(
|
|
||||||
ulong prime,
|
ulong prime,
|
||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
PrimeOrderSearchConfig config,
|
in PrimeOrderSearchConfig config)
|
||||||
|
=> Calculate(prime, previousOrder, divisorData, config, PrimeOrderHeuristicDevice.Cpu);
|
||||||
|
|
||||||
|
public static ulong Calculate(
|
||||||
|
ulong prime,
|
||||||
|
ulong? previousOrder,
|
||||||
|
in MontgomeryDivisorData divisorData,
|
||||||
|
in PrimeOrderSearchConfig config,
|
||||||
PrimeOrderHeuristicDevice device)
|
PrimeOrderHeuristicDevice device)
|
||||||
{
|
{
|
||||||
using var scope = UsePow2Mode(device);
|
var scope = UsePow2Mode(device);
|
||||||
return CalculateInternal(prime, previousOrder, divisorData, config);
|
ulong order = CalculateInternal(prime, previousOrder, divisorData, config);
|
||||||
|
scope.Dispose();
|
||||||
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResult CalculateInternal(ulong prime, ulong? previousOrder, MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (prime <= 3UL)
|
if (prime <= 3UL)
|
||||||
{
|
{
|
||||||
return new PrimeOrderResult(PrimeOrderStatus.Found, prime == 3UL ? 2UL : 1UL);
|
return prime == 3UL ? 2UL : 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong phi = prime - 1UL;
|
ulong phi = prime - 1UL;
|
||||||
|
|
||||||
if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(prime, previousOrder, config, divisorData, out PrimeOrderResult gpuResult))
|
if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(prime, previousOrder, config, divisorData, out ulong gpuOrder))
|
||||||
{
|
{
|
||||||
return gpuResult;
|
return gpuOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
|
||||||
DebugLog("Partial factoring φ(p)");
|
|
||||||
|
|
||||||
PartialFactorResult phiFactors = PartialFactor(phi, config);
|
PartialFactorResult phiFactors = PartialFactor(phi, config);
|
||||||
|
|
||||||
|
ulong result;
|
||||||
if (phiFactors.Factors is null)
|
if (phiFactors.Factors is null)
|
||||||
{
|
{
|
||||||
DebugLog("No factors found");
|
result = FinishStrictlyCpu(prime, divisorData);
|
||||||
return FinishStrictly(prime, config.Mode);
|
phiFactors.Dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return RunHeuristicPipeline(prime, previousOrder, config, divisorData, phi, phiFactors);
|
result = RunHeuristicPipelineCpu(prime, previousOrder, config, divisorData, phi, phiFactors);
|
||||||
|
phiFactors.Dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResult RunHeuristicPipeline(
|
private static ulong RunHeuristicPipelineCpu(
|
||||||
ulong prime,
|
ulong prime,
|
||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
PrimeOrderSearchConfig config,
|
in PrimeOrderSearchConfig config,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
ulong phi,
|
ulong phi,
|
||||||
PartialFactorResult phiFactors)
|
PartialFactorResult phiFactors)
|
||||||
{
|
{
|
||||||
DebugLog("Trying special max check");
|
if (phiFactors.FullyFactored && TrySpecialMaxCpu(phi, prime, phiFactors, divisorData))
|
||||||
if (phiFactors.FullyFactored && TrySpecialMax(phi, prime, phiFactors, divisorData))
|
|
||||||
{
|
{
|
||||||
return new PrimeOrderResult(PrimeOrderStatus.Found, phi);
|
return phi;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugLog("Initializing starting order");
|
ulong candidateOrder = InitializeStartingOrderCpu(prime, phi, divisorData);
|
||||||
ulong candidateOrder = InitializeStartingOrder(prime, phi, divisorData);
|
candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData);
|
||||||
candidateOrder = ExponentLowering(candidateOrder, prime, phiFactors, divisorData);
|
|
||||||
|
|
||||||
DebugLog("Trying to confirm order");
|
if (TryConfirmOrderCpu(prime, candidateOrder, divisorData, config))
|
||||||
if (TryConfirmOrder(prime, candidateOrder, divisorData, config))
|
|
||||||
{
|
{
|
||||||
return new PrimeOrderResult(PrimeOrderStatus.Found, candidateOrder);
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.Mode == PrimeOrderMode.Strict)
|
if (config.Mode == PrimeOrderMode.Strict)
|
||||||
{
|
{
|
||||||
return FinishStrictly(prime, PrimeOrderMode.Strict);
|
return FinishStrictlyCpu(prime, divisorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryHeuristicFinish(prime, candidateOrder, previousOrder, divisorData, config, phiFactors, out ulong order))
|
if (TryHeuristicFinishCpu(prime, candidateOrder, previousOrder, divisorData, config, phiFactors, out ulong order))
|
||||||
{
|
{
|
||||||
return new PrimeOrderResult(PrimeOrderStatus.Found, order);
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugLog("Heuristic unresolved, finishing strictly");
|
return candidateOrder;
|
||||||
return FinishStrictly(prime, config.Mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResult FinishStrictly(ulong prime, PrimeOrderMode mode)
|
private static ulong FinishStrictlyCpu(ulong prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong strictOrder = CalculateByDoubling(prime);
|
return CalculateByFactorizationCpu(prime, divisorData);
|
||||||
return new PrimeOrderResult(mode == PrimeOrderMode.Strict ? PrimeOrderStatus.Found : PrimeOrderStatus.HeuristicUnresolved, strictOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TrySpecialMax(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<FactorEntry> factorSpan = factors.Factors;
|
ReadOnlySpan<FactorEntry> factorSpan = factors.Factors;
|
||||||
int length = factors.Count;
|
int length = factors.Count;
|
||||||
@@ -240,7 +217,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
ulong factor = factorSpan[i].Value;
|
ulong factor = factorSpan[i].Value;
|
||||||
ulong reduced = phi / factor;
|
ulong reduced = phi / factor;
|
||||||
if (Pow2EqualsOne(reduced, prime, divisorData))
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -249,13 +226,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong InitializeStartingOrder(ulong prime, ulong phi, in MontgomeryDivisorData divisorData)
|
private static ulong InitializeStartingOrderCpu(ulong prime, ulong phi, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong order = phi;
|
ulong order = phi;
|
||||||
if ((prime & 7UL) == 1UL || (prime & 7UL) == 7UL)
|
if ((prime & 7UL) == 1UL || (prime & 7UL) == 7UL)
|
||||||
{
|
{
|
||||||
ulong half = phi >> 1;
|
ulong half = phi >> 1;
|
||||||
if (Pow2EqualsOne(half, prime, divisorData))
|
if (Pow2EqualsOneCpu(half, prime, divisorData))
|
||||||
{
|
{
|
||||||
order = half;
|
order = half;
|
||||||
}
|
}
|
||||||
@@ -264,7 +241,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ExponentLowering(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
FactorEntry[]? tempArray = null;
|
FactorEntry[]? tempArray = null;
|
||||||
try
|
try
|
||||||
@@ -297,7 +274,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong reduced = order / primeFactor;
|
ulong reduced = order / primeFactor;
|
||||||
if (Pow2EqualsOne(reduced, prime, divisorData))
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
{
|
{
|
||||||
order = reduced;
|
order = reduced;
|
||||||
continue;
|
continue;
|
||||||
@@ -318,7 +295,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConfirmOrder(ulong prime, ulong order, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
private static bool TryConfirmOrderCpu(ulong prime, ulong order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (order == 0UL)
|
if (order == 0UL)
|
||||||
{
|
{
|
||||||
@@ -326,16 +303,18 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculating `a^order ≡ 1 (mod p)` is a prerequisite for `order` being the actual order of 2 modulo `p`.
|
// Calculating `a^order ≡ 1 (mod p)` is a prerequisite for `order` being the actual order of 2 modulo `p`.
|
||||||
DebugLog("Verifying a^order ≡ 1 (mod p)");
|
// DebugLog("Verifying a^order ≡ 1 (mod p)");
|
||||||
if (!Pow2EqualsOne(order, prime, divisorData))
|
if (!Pow2EqualsOneCpu(order, prime, divisorData))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugLog("Partial factoring order");
|
// DebugLog("Partial factoring order");
|
||||||
|
|
||||||
// TODO: Do we do partial factoring of order multiple times?
|
// TODO: Do we do partial factoring of order multiple times?
|
||||||
PartialFactorResult factorization = PartialFactor(order, config);
|
PartialFactorResult factorization = PartialFactor(order, config);
|
||||||
|
try
|
||||||
|
{
|
||||||
if (factorization.Factors is null)
|
if (factorization.Factors is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -345,12 +324,10 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (factorization.Cofactor <= 1UL)
|
if (factorization.Cofactor <= 1UL)
|
||||||
{
|
{
|
||||||
DebugLog("Cofactor <= 1. No factors found");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use Open.Numerics.Primality for this final check once it's available.
|
// DebugLog("Cofactor > 1. Testing primality of cofactor");
|
||||||
DebugLog("Cofactor > 1. Testing primality of cofactor");
|
|
||||||
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factorization.Cofactor);
|
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factorization.Cofactor);
|
||||||
// bool isPrime = PrimeTester.IsPrimeInternal(factorization.Cofactor, CancellationToken.None);
|
// bool isPrime = PrimeTester.IsPrimeInternal(factorization.Cofactor, CancellationToken.None);
|
||||||
if (!isPrime)
|
if (!isPrime)
|
||||||
@@ -358,12 +335,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugLog("Adding cofactor as prime factor");
|
// DebugLog("Adding cofactor as prime factor");
|
||||||
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
|
PartialFactorResult extended = factorization.WithAdditionalPrime(factorization.Cofactor);
|
||||||
|
factorization.Dispose();
|
||||||
|
factorization = extended;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<FactorEntry> span = factorization.Factors;
|
ReadOnlySpan<FactorEntry> span = factorization.Factors;
|
||||||
DebugLog("Verifying prime-power reductions");
|
// DebugLog("Verifying prime-power reductions");
|
||||||
int length = factorization.Count;
|
int length = factorization.Count;
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
@@ -377,7 +356,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduced /= primeFactor;
|
reduced /= primeFactor;
|
||||||
if (Pow2EqualsOne(reduced, prime, divisorData))
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -386,13 +365,18 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
factorization.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryHeuristicFinish(
|
private static bool TryHeuristicFinishCpu(
|
||||||
ulong prime,
|
ulong prime,
|
||||||
ulong order,
|
ulong order,
|
||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
PrimeOrderSearchConfig config,
|
in PrimeOrderSearchConfig config,
|
||||||
PartialFactorResult phiFactors,
|
PartialFactorResult phiFactors,
|
||||||
out ulong result)
|
out ulong result)
|
||||||
{
|
{
|
||||||
@@ -403,8 +387,10 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Do we do partial factoring of order multiple times?
|
// TODO: Do we do partial factoring of order multiple times?
|
||||||
DebugLog("Trying heuristic. Partial factoring order");
|
// DebugLog("Trying heuristic. Partial factoring order");
|
||||||
PartialFactorResult orderFactors = PartialFactor(order, config);
|
PartialFactorResult orderFactors = PartialFactor(order, config);
|
||||||
|
try
|
||||||
|
{
|
||||||
if (orderFactors.Factors is null)
|
if (orderFactors.Factors is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -424,20 +410,22 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
orderFactors = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
PartialFactorResult extended = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
||||||
|
orderFactors.Dispose();
|
||||||
|
orderFactors = extended;
|
||||||
}
|
}
|
||||||
|
|
||||||
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
|
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
|
||||||
List<ulong> candidates = new(capacity);
|
List<ulong> candidates = new(capacity);
|
||||||
FactorEntry[] factorArray = orderFactors.Factors!;
|
FactorEntry[] factorArray = orderFactors.Factors!;
|
||||||
DebugLog("Building candidates list");
|
// DebugLog("Building candidates list");
|
||||||
BuildCandidates(order, factorArray, orderFactors.Count, candidates, capacity);
|
BuildCandidates(order, factorArray, orderFactors.Count, candidates, capacity);
|
||||||
if (candidates.Count == 0)
|
if (candidates.Count == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugLog("Sorting candidates");
|
// DebugLog("Sorting candidates");
|
||||||
SortCandidates(prime, previousOrder, candidates);
|
SortCandidates(prime, previousOrder, candidates);
|
||||||
|
|
||||||
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
|
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
|
||||||
@@ -446,7 +434,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
bool allowGpuBatch = true;
|
bool allowGpuBatch = true;
|
||||||
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
||||||
|
|
||||||
DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)");
|
// DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)");
|
||||||
int index = 0;
|
int index = 0;
|
||||||
const int MaxGpuBatchSize = 1024;
|
const int MaxGpuBatchSize = 1024;
|
||||||
const int StackGpuBatchSize = 1024;
|
const int StackGpuBatchSize = 1024;
|
||||||
@@ -516,14 +504,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
equalsOne = Pow2EqualsOne(candidate, prime, divisorData);
|
equalsOne = Pow2EqualsOneCpu(candidate, prime, divisorData);
|
||||||
}
|
}
|
||||||
if (!equalsOne)
|
if (!equalsOne)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConfirmCandidate(prime, candidate, divisorData, config, ref powUsed, powBudget))
|
if (!TryConfirmCandidateCpu(prime, candidate, divisorData, config, ref powUsed, powBudget))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -545,9 +533,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
index += batchSize;
|
index += batchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugLog("No candidate confirmed");
|
// DebugLog("No candidate confirmed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
orderFactors.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void SortCandidates(ulong prime, ulong? previousOrder, List<ulong> candidates)
|
private static void SortCandidates(ulong prime, ulong? previousOrder, List<ulong> candidates)
|
||||||
{
|
{
|
||||||
@@ -716,9 +709,11 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConfirmCandidate(ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config, ref int powUsed, int powBudget)
|
private static bool TryConfirmCandidateCpu(ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget)
|
||||||
{
|
{
|
||||||
PartialFactorResult factorization = PartialFactor(candidate, config);
|
PartialFactorResult factorization = PartialFactor(candidate, config);
|
||||||
|
try
|
||||||
|
{
|
||||||
if (factorization.Factors is null)
|
if (factorization.Factors is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -738,7 +733,9 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
factorization = factorization.WithAdditionalPrime(factorization.Cofactor);
|
PartialFactorResult extended = factorization.WithAdditionalPrime(factorization.Cofactor);
|
||||||
|
factorization.Dispose();
|
||||||
|
factorization = extended;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<FactorEntry> span = factorization.Factors;
|
ReadOnlySpan<FactorEntry> span = factorization.Factors;
|
||||||
@@ -761,7 +758,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
powUsed++;
|
powUsed++;
|
||||||
if (Pow2EqualsOne(reduced, prime, divisorData))
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -770,8 +767,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
factorization.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool Pow2EqualsOne(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData)
|
private static bool Pow2EqualsOneCpu(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
if (IsGpuPow2Allowed)
|
if (IsGpuPow2Allowed)
|
||||||
{
|
{
|
||||||
@@ -782,10 +784,59 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PartialFactorResult PartialFactor(ulong value, PrimeOrderSearchConfig config)
|
private static List<ulong> AcquirePendingCompositeList(int capacityHint)
|
||||||
|
{
|
||||||
|
List<ulong>? list = s_partialFactorPending;
|
||||||
|
if (list is null)
|
||||||
|
{
|
||||||
|
list = new List<ulong>(capacityHint);
|
||||||
|
s_partialFactorPending = list;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
if (list.Capacity < capacityHint)
|
||||||
|
{
|
||||||
|
list.Capacity = capacityHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stack<ulong> AcquireCompositeStack(int capacityHint)
|
||||||
|
{
|
||||||
|
Stack<ulong>? stack = s_partialFactorCompositeStack;
|
||||||
|
if (stack is null)
|
||||||
|
{
|
||||||
|
stack = new Stack<ulong>(capacityHint);
|
||||||
|
s_partialFactorCompositeStack = stack;
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.Clear();
|
||||||
|
stack.EnsureCapacity(capacityHint);
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<ulong, int> AcquireFactorCountDictionary(int capacityHint)
|
||||||
|
{
|
||||||
|
Dictionary<ulong, int>? dictionary = s_partialFactorCounts;
|
||||||
|
if (dictionary is null)
|
||||||
|
{
|
||||||
|
dictionary = new Dictionary<ulong, int>(capacityHint);
|
||||||
|
s_partialFactorCounts = dictionary;
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary.Clear();
|
||||||
|
dictionary.EnsureCapacity(capacityHint);
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PartialFactorResult PartialFactor(ulong value, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (value <= 1UL)
|
if (value <= 1UL)
|
||||||
{
|
{
|
||||||
@@ -803,18 +854,19 @@ internal static partial class PrimeOrderCalculator
|
|||||||
bool useDictionary = false;
|
bool useDictionary = false;
|
||||||
|
|
||||||
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
|
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
|
||||||
ulong remaining;
|
ulong remaining = value;
|
||||||
|
|
||||||
bool gpuFactored = false;
|
bool gpuFactored = false;
|
||||||
if (IsGpuHeuristicDevice)
|
if (IsGpuHeuristicDevice)
|
||||||
{
|
{
|
||||||
gpuFactored = PrimeOrderGpuHeuristics.TryPartialFactor(value, limit, primeSlots, exponentSlots, out factorCount, out remaining, out _);
|
gpuFactored = PrimeOrderGpuHeuristics.TryPartialFactor(value, limit, primeSlots, exponentSlots, out factorCount, out remaining, out _);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
remaining = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
List<ulong> pending = AcquirePendingCompositeList(2);
|
||||||
|
Stack<ulong>? compositeStack = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
if (!gpuFactored)
|
if (!gpuFactored)
|
||||||
{
|
{
|
||||||
primeSlots.Clear();
|
primeSlots.Clear();
|
||||||
@@ -822,12 +874,11 @@ internal static partial class PrimeOrderCalculator
|
|||||||
if (!TryPopulateSmallPrimeFactorsCpu(value, limit, primeSlots, exponentSlots, out factorCount, out remaining))
|
if (!TryPopulateSmallPrimeFactorsCpu(value, limit, primeSlots, exponentSlots, out factorCount, out remaining))
|
||||||
{
|
{
|
||||||
useDictionary = true;
|
useDictionary = true;
|
||||||
counts = new Dictionary<ulong, int>(capacity: 8);
|
counts = AcquireFactorCountDictionary(Math.Max(factorCount, 8));
|
||||||
remaining = PopulateSmallPrimeFactorsCpu(value, limit, counts);
|
remaining = PopulateSmallPrimeFactorsCpu(value, limit, counts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ulong> pending = new();
|
|
||||||
if (remaining > 1UL)
|
if (remaining > 1UL)
|
||||||
{
|
{
|
||||||
pending.Add(remaining);
|
pending.Add(remaining);
|
||||||
@@ -835,16 +886,15 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
if (config.PollardRhoMilliseconds > 0 && pending.Count > 0)
|
if (config.PollardRhoMilliseconds > 0 && pending.Count > 0)
|
||||||
{
|
{
|
||||||
DebugLog("Processing pending composites with Pollard's Rho");
|
|
||||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||||
long budgetTicks = TimeSpan.FromMilliseconds(config.PollardRhoMilliseconds).Ticks;
|
long budgetTicks = TimeSpan.FromMilliseconds(config.PollardRhoMilliseconds).Ticks;
|
||||||
Stack<ulong> stack = new();
|
compositeStack = AcquireCompositeStack(Math.Max(pending.Count * 2, 4));
|
||||||
stack.Push(remaining);
|
compositeStack.Push(remaining);
|
||||||
pending.Clear();
|
pending.Clear();
|
||||||
|
|
||||||
while (stack.Count > 0)
|
while (compositeStack.Count > 0)
|
||||||
{
|
{
|
||||||
ulong composite = stack.Pop();
|
ulong composite = compositeStack.Pop();
|
||||||
if (composite <= 1UL)
|
if (composite <= 1UL)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -870,8 +920,8 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong quotient = composite / factor;
|
ulong quotient = composite / factor;
|
||||||
stack.Push(factor);
|
compositeStack.Push(factor);
|
||||||
stack.Push(quotient);
|
compositeStack.Push(quotient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,18 +945,16 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if ((counts is null || counts.Count == 0) && cofactor == value)
|
if ((counts is null || counts.Count == 0) && cofactor == value)
|
||||||
{
|
{
|
||||||
DebugLog("cofactor is the same as value, no factors found");
|
return PartialFactorResult.Rent(null, value, false, 0);
|
||||||
return new PartialFactorResult(null, value, false, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (counts is null || counts.Count == 0)
|
if (counts is null || counts.Count == 0)
|
||||||
{
|
{
|
||||||
return new PartialFactorResult(null, cofactor, cofactor == 1UL, 0);
|
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry[] factors = ArrayPool<FactorEntry>.Shared.Rent(counts.Count);
|
FactorEntry[] factors = ArrayPool<FactorEntry>.Shared.Rent(counts.Count);
|
||||||
int index = 0;
|
int index = 0;
|
||||||
DebugLog(() => $"Collecting {counts.Count} prime factors");
|
|
||||||
foreach (KeyValuePair<ulong, int> entry in counts)
|
foreach (KeyValuePair<ulong, int> entry in counts)
|
||||||
{
|
{
|
||||||
factors[index] = new FactorEntry(entry.Key, entry.Value);
|
factors[index] = new FactorEntry(entry.Key, entry.Value);
|
||||||
@@ -916,12 +964,8 @@ internal static partial class PrimeOrderCalculator
|
|||||||
Span<FactorEntry> span = factors.AsSpan(0, index);
|
Span<FactorEntry> span = factors.AsSpan(0, index);
|
||||||
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
|
|
||||||
FactorEntry[] resultArray = new FactorEntry[index];
|
|
||||||
span.CopyTo(resultArray);
|
|
||||||
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
|
|
||||||
|
|
||||||
bool fullyFactored = cofactor == 1UL;
|
bool fullyFactored = cofactor == 1UL;
|
||||||
return new PartialFactorResult(resultArray, cofactor, fullyFactored, index);
|
return PartialFactorResult.Rent(factors, cofactor, fullyFactored, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
int actualCount = 0;
|
int actualCount = 0;
|
||||||
@@ -937,16 +981,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (cofactor == value)
|
if (cofactor == value)
|
||||||
{
|
{
|
||||||
DebugLog("cofactor is the same as value, no factors found");
|
return PartialFactorResult.Rent(null, value, false, 0);
|
||||||
return new PartialFactorResult(null, value, false, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PartialFactorResult(null, cofactor, cofactor == 1UL, 0);
|
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry[] array = new FactorEntry[actualCount];
|
FactorEntry[] array = ArrayPool<FactorEntry>.Shared.Rent(actualCount);
|
||||||
int arrayIndex = 0;
|
int arrayIndex = 0;
|
||||||
DebugLog(() => $"Collecting {actualCount} prime factors");
|
|
||||||
for (int i = 0; i < factorCount; i++)
|
for (int i = 0; i < factorCount; i++)
|
||||||
{
|
{
|
||||||
ulong primeValue = primeSlots[i];
|
ulong primeValue = primeSlots[i];
|
||||||
@@ -960,9 +1002,17 @@ internal static partial class PrimeOrderCalculator
|
|||||||
arrayIndex++;
|
arrayIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.Sort(array, static (a, b) => a.Value.CompareTo(b.Value));
|
Span<FactorEntry> arraySpan = array.AsSpan(0, arrayIndex);
|
||||||
|
arraySpan.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
bool fullyFactoredArray = cofactor == 1UL;
|
bool fullyFactoredArray = cofactor == 1UL;
|
||||||
return new PartialFactorResult(array, cofactor, fullyFactoredArray, array.Length);
|
return PartialFactorResult.Rent(array, cofactor, fullyFactoredArray, arrayIndex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
pending.Clear();
|
||||||
|
counts?.Clear();
|
||||||
|
compositeStack?.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const int GpuSmallPrimeFactorSlots = 64;
|
private const int GpuSmallPrimeFactorSlots = 64;
|
||||||
@@ -1114,7 +1164,9 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<byte> buffer = stackalloc byte[8];
|
PollardRhoMontgomeryReducer reducer = PollardRhoMontgomeryReducer.Create(n);
|
||||||
|
ulong diff = 0UL;
|
||||||
|
ulong d = 0UL;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -1123,12 +1175,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RandomNumberGenerator.Fill(buffer);
|
ulong c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
|
||||||
ulong c = (BinaryPrimitives.ReadUInt64LittleEndian(buffer) % (n - 1UL)) + 1UL;
|
ulong xStandard = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
|
||||||
RandomNumberGenerator.Fill(buffer);
|
ulong yStandard = xStandard;
|
||||||
ulong x = (BinaryPrimitives.ReadUInt64LittleEndian(buffer) % (n - 2UL)) + 2UL;
|
ulong cMontgomery = reducer.ConvertToMontgomery(c);
|
||||||
ulong y = x;
|
ulong xMontgomery = reducer.ConvertToMontgomery(xStandard);
|
||||||
ulong d = 1UL;
|
ulong yMontgomery = xMontgomery;
|
||||||
|
d = 1UL;
|
||||||
|
|
||||||
while (d == 1UL)
|
while (d == 1UL)
|
||||||
{
|
{
|
||||||
@@ -1137,10 +1190,15 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
x = AdvancePolynomial(x, c, n);
|
xMontgomery = reducer.Advance(xMontgomery, cMontgomery);
|
||||||
y = AdvancePolynomial(y, c, n);
|
xStandard = reducer.AdvanceStandard(xStandard, c);
|
||||||
y = AdvancePolynomial(y, c, n);
|
|
||||||
ulong diff = x > y ? x - y : y - x;
|
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
|
||||||
|
yStandard = reducer.AdvanceStandard(yStandard, c);
|
||||||
|
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
|
||||||
|
yStandard = reducer.AdvanceStandard(yStandard, c);
|
||||||
|
|
||||||
|
diff = xStandard > yStandard ? xStandard - yStandard : yStandard - xStandard;
|
||||||
d = BinaryGcd(diff, n);
|
d = BinaryGcd(diff, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1152,13 +1210,6 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong AdvancePolynomial(ulong x, ulong c, ulong modulus)
|
|
||||||
{
|
|
||||||
UInt128 value = (UInt128)x * x + c;
|
|
||||||
return (ulong)(value % modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong BinaryGcd(ulong a, ulong b)
|
private static ulong BinaryGcd(ulong a, ulong b)
|
||||||
{
|
{
|
||||||
if (a == 0UL)
|
if (a == 0UL)
|
||||||
@@ -1254,7 +1305,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
counts ??= new Dictionary<ulong, int>(Math.Max(count, 8));
|
counts ??= AcquireFactorCountDictionary(Math.Max(count, 8));
|
||||||
CopyFactorsToDictionary(primes, exponents, count, counts);
|
CopyFactorsToDictionary(primes, exponents, count, counts);
|
||||||
count = 0;
|
count = 0;
|
||||||
useDictionary = true;
|
useDictionary = true;
|
||||||
@@ -1280,59 +1331,110 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong CalculateByDoubling(ulong prime)
|
private static ulong CalculateByFactorizationCpu(ulong prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong order = 1UL;
|
ulong phi = prime - 1UL;
|
||||||
ulong pow = 2UL;
|
Dictionary<ulong, int> counts = new(capacity: 8);
|
||||||
|
FactorCompletely(phi, counts);
|
||||||
while (pow != 1UL)
|
if (counts.Count == 0)
|
||||||
{
|
{
|
||||||
pow <<= 1;
|
return phi;
|
||||||
if (pow >= prime)
|
|
||||||
{
|
|
||||||
pow -= prime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
order++;
|
List<KeyValuePair<ulong, int>> entries = new(counts);
|
||||||
|
entries.Sort(static (a, b) => a.Key.CompareTo(b.Key));
|
||||||
|
|
||||||
|
ulong order = phi;
|
||||||
|
int entryCount = entries.Count;
|
||||||
|
for (int i = 0; i < entryCount; i++)
|
||||||
|
{
|
||||||
|
ulong primeFactor = entries[i].Key;
|
||||||
|
int exponent = entries[i].Value;
|
||||||
|
for (int iteration = 0; iteration < exponent; iteration++)
|
||||||
|
{
|
||||||
|
if ((order % primeFactor) != 0UL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong candidate = order / primeFactor;
|
||||||
|
if (Pow2EqualsOneCpu(candidate, prime, divisorData))
|
||||||
|
{
|
||||||
|
order = candidate;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void FactorCompletely(ulong value, Dictionary<ulong, int> counts)
|
||||||
|
{
|
||||||
|
if (value <= 1UL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Open.Numeric.Primes.Prime.Numbers.IsPrime(value))
|
||||||
|
{
|
||||||
|
AddFactor(counts, value, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong factor = PollardRhoStrict(value);
|
||||||
|
FactorCompletely(factor, counts);
|
||||||
|
FactorCompletely(value / factor, counts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong PollardRhoStrict(ulong n)
|
||||||
|
{
|
||||||
|
if ((n & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
return 2UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PollardRhoMontgomeryReducer reducer = PollardRhoMontgomeryReducer.Create(n);
|
||||||
|
ulong diff = 0UL;
|
||||||
|
ulong d = 0UL;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ulong c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
|
||||||
|
ulong xStandard = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
|
||||||
|
ulong yStandard = xStandard;
|
||||||
|
ulong cMontgomery = reducer.ConvertToMontgomery(c);
|
||||||
|
ulong xMontgomery = reducer.ConvertToMontgomery(xStandard);
|
||||||
|
ulong yMontgomery = xMontgomery;
|
||||||
|
d = 1UL;
|
||||||
|
|
||||||
|
while (d == 1UL)
|
||||||
|
{
|
||||||
|
xMontgomery = reducer.Advance(xMontgomery, cMontgomery);
|
||||||
|
xStandard = reducer.AdvanceStandard(xStandard, c);
|
||||||
|
|
||||||
|
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
|
||||||
|
yStandard = reducer.AdvanceStandard(yStandard, c);
|
||||||
|
yMontgomery = reducer.Advance(yMontgomery, cMontgomery);
|
||||||
|
yStandard = reducer.AdvanceStandard(yStandard, c);
|
||||||
|
|
||||||
|
diff = xStandard > yStandard ? xStandard - yStandard : yStandard - xStandard;
|
||||||
|
d = BinaryGcd(diff, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d != n)
|
||||||
|
{
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly struct CandidateKey(int primary, long secondary, long tertiary)
|
private readonly struct CandidateKey(int primary, long secondary, long tertiary)
|
||||||
{
|
{
|
||||||
public readonly int Primary = primary;
|
public readonly int Primary = primary;
|
||||||
public readonly long Secondary = secondary;
|
public readonly long Secondary = secondary;
|
||||||
public readonly long Tertiary = tertiary;
|
public readonly long Tertiary = tertiary;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct FactorEntry(ulong value, int exponent)
|
|
||||||
{
|
|
||||||
public readonly ulong Value = value;
|
|
||||||
public readonly int Exponent = exponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct PartialFactorResult(FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count)
|
|
||||||
{
|
|
||||||
public readonly FactorEntry[]? Factors = factors;
|
|
||||||
public readonly ulong Cofactor = cofactor;
|
|
||||||
public readonly bool FullyFactored = fullyFactored;
|
|
||||||
public readonly int Count = count;
|
|
||||||
public readonly static PartialFactorResult Empty = new(null, 1UL, true, 0);
|
|
||||||
|
|
||||||
public PartialFactorResult WithAdditionalPrime(ulong prime)
|
|
||||||
{
|
|
||||||
if (Factors is null)
|
|
||||||
{
|
|
||||||
FactorEntry[] local = [new FactorEntry(prime, 1)];
|
|
||||||
return new PartialFactorResult(local, 1UL, true, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
FactorEntry[] extended = new FactorEntry[Count + 1];
|
|
||||||
Array.Copy(Factors, 0, extended, 0, Count);
|
|
||||||
extended[Count] = new FactorEntry(prime, 1);
|
|
||||||
Array.Sort(extended, static (a, b) => a.Value.CompareTo(b.Value));
|
|
||||||
return new PartialFactorResult(extended, 1UL, true, Count + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
internal static class ReciprocalMath
|
||||||
|
{
|
||||||
|
private const int ReciprocalShift = 64;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong ComputeReciprocalEstimate(ulong divisor)
|
||||||
|
{
|
||||||
|
if (divisor <= 1UL)
|
||||||
|
{
|
||||||
|
return ulong.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 scaledOne = UInt128.One << ReciprocalShift;
|
||||||
|
UInt128 estimate = scaledOne / divisor;
|
||||||
|
if ((scaledOne % divisor) != UInt128.Zero)
|
||||||
|
{
|
||||||
|
estimate += UInt128.One;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estimate == UInt128.Zero)
|
||||||
|
{
|
||||||
|
return 1UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estimate > ulong.MaxValue)
|
||||||
|
{
|
||||||
|
return ulong.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ulong)estimate;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong RefineReciprocalEstimate(ulong reciprocal, ulong divisor)
|
||||||
|
{
|
||||||
|
if (divisor <= 1UL)
|
||||||
|
{
|
||||||
|
return ulong.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
return ComputeReciprocalEstimate(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 reciprocal128 = reciprocal;
|
||||||
|
UInt128 product = (UInt128)divisor * reciprocal128;
|
||||||
|
UInt128 scaled = product >> ReciprocalShift;
|
||||||
|
UInt128 twoScaled = UInt128.One << (ReciprocalShift + 1);
|
||||||
|
if (scaled >= twoScaled)
|
||||||
|
{
|
||||||
|
return ComputeReciprocalEstimate(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 correction = twoScaled - scaled;
|
||||||
|
UInt128 refined = (reciprocal128 * correction) >> ReciprocalShift;
|
||||||
|
if (refined == UInt128.Zero)
|
||||||
|
{
|
||||||
|
return 1UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return refined > ulong.MaxValue ? ulong.MaxValue : (ulong)refined;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Reduce(UInt128 numerator, ulong divisor, ulong reciprocal)
|
||||||
|
{
|
||||||
|
if (divisor <= 1UL)
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numerator == UInt128.Zero)
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
return (ulong)(numerator % divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong high = (ulong)(numerator >> ReciprocalShift);
|
||||||
|
ulong low = (ulong)numerator;
|
||||||
|
|
||||||
|
if (high >= divisor)
|
||||||
|
{
|
||||||
|
return (ulong)(numerator % divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 highProduct = (UInt128)high * reciprocal;
|
||||||
|
UInt128 lowProduct = (UInt128)low * reciprocal;
|
||||||
|
UInt128 estimate = highProduct + (lowProduct >> ReciprocalShift);
|
||||||
|
|
||||||
|
if (estimate == UInt128.Zero)
|
||||||
|
{
|
||||||
|
return (ulong)(numerator % divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 product = estimate * divisor;
|
||||||
|
while (product > numerator)
|
||||||
|
{
|
||||||
|
if (estimate == UInt128.Zero)
|
||||||
|
{
|
||||||
|
return (ulong)(numerator % divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
estimate--;
|
||||||
|
product -= divisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 remainder = numerator - product;
|
||||||
|
while (remainder >= divisor)
|
||||||
|
{
|
||||||
|
remainder -= divisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ulong)remainder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -218,7 +218,7 @@ public static class UInt128Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||||
{
|
{
|
||||||
if (bitIndex >= 64)
|
if (bitIndex >= 64)
|
||||||
{
|
{
|
||||||
@@ -229,18 +229,16 @@ public static class UInt128Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||||
{
|
{
|
||||||
|
ulong mask = (1UL << windowBitCount) - 1UL;
|
||||||
if (windowStart != 0)
|
if (windowStart != 0)
|
||||||
{
|
{
|
||||||
GpuUInt128 shifted = exponent;
|
exponent.ShiftRight(windowStart);
|
||||||
shifted.ShiftRight(windowStart);
|
return exponent.Low & mask;
|
||||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
|
||||||
return shifted.Low & mask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong directMask = (1UL << windowBitCount) - 1UL;
|
return exponent.Low & mask;
|
||||||
return exponent.Low & directMask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using ILGPU.Algorithms;
|
using ILGPU.Algorithms;
|
||||||
@@ -326,7 +327,7 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModWindowed(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
public static ulong Pow2MontgomeryModWindowedCpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
if (exponent == 0UL)
|
if (exponent == 0UL)
|
||||||
@@ -345,7 +346,78 @@ public static class ULongExtensions
|
|||||||
|
|
||||||
ulong result = divisor.MontgomeryOne;
|
ulong result = divisor.MontgomeryOne;
|
||||||
ulong nPrime = divisor.NPrime;
|
ulong nPrime = divisor.NPrime;
|
||||||
ulong[] oddPowers = InitializeMontgomeryOddPowers(divisor, modulus, nPrime, oddPowerCount);
|
ulong[] oddPowers = InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowerCount);
|
||||||
|
|
||||||
|
int index = bitLength - 1;
|
||||||
|
while (index >= 0)
|
||||||
|
{
|
||||||
|
if (((exponent >> index) & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
|
index--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int windowStart = index - windowSize + 1;
|
||||||
|
if (windowStart < 0)
|
||||||
|
{
|
||||||
|
windowStart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (((exponent >> windowStart) & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
windowStart++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int windowLength = index - windowStart + 1;
|
||||||
|
for (int square = 0; square < windowLength; square++)
|
||||||
|
{
|
||||||
|
result = result.MontgomeryMultiply(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.MontgomeryMultiply(multiplier, modulus, nPrime);
|
||||||
|
|
||||||
|
index = windowStart - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oddPowerCount >= PerfectNumberConstants.PooledArrayThreshold)
|
||||||
|
{
|
||||||
|
ArrayPool<ulong>.Shared.Return(oddPowers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepMontgomery)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2MontgomeryModWindowedGpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||||
|
{
|
||||||
|
ulong modulus = divisor.Modulus;
|
||||||
|
if (exponent == 0UL)
|
||||||
|
{
|
||||||
|
return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exponent <= Pow2WindowFallbackThreshold)
|
||||||
|
{
|
||||||
|
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitLength = GetPortableBitLength(exponent);
|
||||||
|
int windowSize = GetWindowSize(bitLength);
|
||||||
|
int oddPowerCount = 1 << (windowSize - 1);
|
||||||
|
|
||||||
|
ulong result = divisor.MontgomeryOne;
|
||||||
|
ulong nPrime = divisor.NPrime;
|
||||||
|
ulong[] oddPowers = InitializeMontgomeryOddPowersGpu(divisor, modulus, nPrime, oddPowerCount);
|
||||||
|
|
||||||
int index = bitLength - 1;
|
int index = bitLength - 1;
|
||||||
while (index >= 0)
|
while (index >= 0)
|
||||||
@@ -461,7 +533,30 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static ulong[] InitializeMontgomeryOddPowers(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
private static ulong[] InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
||||||
|
{
|
||||||
|
ulong[] oddPowers = oddPowerCount < PerfectNumberConstants.PooledArrayThreshold ? new ulong[oddPowerCount] : ArrayPool<ulong>.Shared.Rent(oddPowerCount);
|
||||||
|
|
||||||
|
oddPowers[0] = divisor.MontgomeryTwo;
|
||||||
|
if (oddPowerCount == 1)
|
||||||
|
{
|
||||||
|
return oddPowers;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong previous,
|
||||||
|
square = divisor.MontgomeryTwoSquared;
|
||||||
|
|
||||||
|
for (int i = 1; i < oddPowerCount; i++)
|
||||||
|
{
|
||||||
|
previous = oddPowers[i - 1];
|
||||||
|
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return oddPowers;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong[] InitializeMontgomeryOddPowersGpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
||||||
{
|
{
|
||||||
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
||||||
oddPowers[0] = divisor.MontgomeryTwo;
|
oddPowers[0] = divisor.MontgomeryTwo;
|
||||||
@@ -481,17 +576,31 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModWithCycle(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
public static ulong Pow2MontgomeryModWithCycleCpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
ulong rotationCount = exponent % cycleLength;
|
ulong rotationCount = exponent % cycleLength;
|
||||||
return Pow2MontgomeryModWindowed(rotationCount, divisor, keepMontgomery: false);
|
return Pow2MontgomeryModWindowedCpu(rotationCount, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModFromCycleRemainder(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
public static ulong Pow2MontgomeryModFromCycleRemainderCpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
return Pow2MontgomeryModWindowed(reducedExponent, divisor, keepMontgomery: false);
|
return Pow2MontgomeryModWindowedCpu(reducedExponent, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||||
|
{
|
||||||
|
ulong rotationCount = exponent % cycleLength;
|
||||||
|
return Pow2MontgomeryModWindowedGpu(rotationCount, divisor, keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2MontgomeryModFromCycleRemainderGpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||||
|
{
|
||||||
|
return Pow2MontgomeryModWindowedGpu(reducedExponent, divisor, keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
# CPU `--mersenne=bydivisor` pipeline notes
|
||||||
|
|
||||||
|
This document summarizes how the `EvenPerfectBitScanner` executable executes the
|
||||||
|
`--mersenne=bydivisor` mode on the CPU, and how the analytic observations we
|
||||||
|
derived for the quotients
|
||||||
|
|
||||||
|
\[
|
||||||
|
Q_k = \frac{N}{d_k}, \qquad d_k = 2pk + 1, \qquad N = 2^p - 1,
|
||||||
|
\]
|
||||||
|
|
||||||
|
can help us identify concrete optimization opportunities.
|
||||||
|
|
||||||
|
## High-level control flow
|
||||||
|
|
||||||
|
* `Program.Main` wires `_byDivisorTester` to a CPU implementation whenever the
|
||||||
|
CLI switches resolve to `--mersenne=bydivisor` and
|
||||||
|
`--mersenne-device=cpu`. The CLI also forces a block size of 1 so that the
|
||||||
|
pipeline pushes exponents to the tester one at a time.
|
||||||
|
(`Program.cs`, lines 459-520.)
|
||||||
|
* When the mode starts, `RunByDivisorMode` delegates to
|
||||||
|
`MersenneNumberDivisorByDivisorTester.Run`, passing the candidate list from
|
||||||
|
`--filter-p`, the CPU tester instance and the optional replay of previous
|
||||||
|
outcomes. (`Program.cs`, lines 809-826.)
|
||||||
|
* `MersenneNumberDivisorByDivisorTester.Run` sorts and deduplicates the
|
||||||
|
candidate exponents, drops the ones already confirmed in prior runs, validates
|
||||||
|
primality with the `Open.Numeric.Primes` enumerator and configures the tester
|
||||||
|
with the largest remaining exponent. (`MersenneNumberDivisorByDivisorTester.cs`,
|
||||||
|
lines 14-162.)
|
||||||
|
* For each confirmed prime exponent, the CPU tester computes the largest divisor
|
||||||
|
it needs to probe through `ComputeAllowedMaxDivisor`. If the range is non-empty
|
||||||
|
it enters the `CheckDivisors` loop. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||||
|
lines 83-110 and 161-266.)
|
||||||
|
|
||||||
|
## Inner loop behaviour (`CheckDivisors`)
|
||||||
|
|
||||||
|
The hot path is the `while (divisor <= limit)` loop in
|
||||||
|
`MersenneNumberDivisorByDivisorCpuTester.CheckDivisors`.
|
||||||
|
|
||||||
|
1. The loop enumerates divisor candidates of the form `d_k = 2pk + 1` by
|
||||||
|
incrementing `divisor` by `step = 2p` every iteration. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||||
|
lines 177-256.)
|
||||||
|
2. Small modulus filters track `d_k (mod 3·5·7·8·10·11)` to discard impossible
|
||||||
|
candidates without touching the divisor-cycle cache. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||||
|
lines 195-263.)
|
||||||
|
3. For every admissible divisor the tester fetches its Montgomery data and the
|
||||||
|
cached cycle length. When the cache does not contain an entry the
|
||||||
|
implementation recomputes the cycle by factoring the divisor. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||||
|
lines 223-254.)
|
||||||
|
4. A cycle equal to the tested exponent proves divisibility, so the loop aborts
|
||||||
|
as soon as one hit is found. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||||
|
lines 241-247.)
|
||||||
|
|
||||||
|
`DivisorScanSession.CheckDivisor` follows the same `d_k` progression when a
|
||||||
|
batch API is used. It keeps a running Montgomery residue via
|
||||||
|
`ExponentRemainderStepper`, but the stepper currently recomputes the delta by
|
||||||
|
calling a full `Pow2MontgomeryModWindowed` whenever the exponent jumps.
|
||||||
|
(`MersenneNumberDivisorByDivisorCpuTester.cs`, lines 358-405 and
|
||||||
|
`ExponentRemainderStepper.cs`, lines 26-95.)
|
||||||
|
|
||||||
|
## Where the analytic model fits
|
||||||
|
|
||||||
|
The formulas we established for the quotient sequence
|
||||||
|
|
||||||
|
\[
|
||||||
|
\frac{Q_{k+1}}{Q_k} = \frac{2pk + 1}{2p(k+1) + 1}, \qquad
|
||||||
|
Q_k - Q_{k+1} = \frac{N·2p}{(2pk + 1)(2p(k+1) + 1)}
|
||||||
|
\]
|
||||||
|
|
||||||
|
tell us that the successive quotients and remainders shrink smoothly and that the
|
||||||
|
per-step update depends only on the previous `(Q_k, r_k)` state and the next
|
||||||
|
`d_{k+1}`. This lines up with several TODO comments that highlight missing
|
||||||
|
incremental updates:
|
||||||
|
|
||||||
|
* `ExponentRemainderStepper` already caches the previous exponent and a running
|
||||||
|
Montgomery residue, but it falls back to a full modular exponentiation to
|
||||||
|
compute the delta between consecutive divisors. (`ExponentRemainderStepper.cs`,
|
||||||
|
lines 49-72.)
|
||||||
|
* `CheckDivisors` rebuilds the Montgomery data for every hit candidate even
|
||||||
|
though the sequence of `d_k` values is strictly monotone.
|
||||||
|
|
||||||
|
The quotient identity gives us a way to compute `Δ_k = Q_k - Q_{k+1}` by reading
|
||||||
|
`Q_k` and the inexpensive division `(2pQ_k - r_k) / d_{k+1}`. Once we know `Δ_k`
|
||||||
|
we can update
|
||||||
|
|
||||||
|
\[
|
||||||
|
Q_{k+1} = Q_k - Δ_k, \qquad r_{k+1} = r_k - 2p·Q_k + Δ_k · d_{k+1},
|
||||||
|
\]
|
||||||
|
|
||||||
|
without ever evaluating `2^p mod d_{k+1}` from scratch. Incorporating this into
|
||||||
|
`ExponentRemainderStepper` would let the CPU path reuse a single Montgomery state
|
||||||
|
across the entire scan and swap the current delta exponentiation (which costs a
|
||||||
|
full modular ladder) for one multiply-high with a precomputed reciprocal of
|
||||||
|
`d_{k+1}`. The loop already maintains `d_k` and `step`, so the extra bookkeeping
|
||||||
|
fits naturally.
|
||||||
|
|
||||||
|
## Optimization ideas grounded in the code
|
||||||
|
|
||||||
|
1. **Delta-driven Montgomery updates.** Extend `DivisorScanSession` so it tracks
|
||||||
|
`(Q_k, r_k)` alongside the Montgomery residue and uses the analytic delta
|
||||||
|
formula to derive the next residue. This replaces the per-step powmod in
|
||||||
|
`ExponentRemainderStepper.ComputeNext` with a handful of 64-bit operations.
|
||||||
|
(`MersenneNumberDivisorByDivisorCpuTester.cs`, lines 358-405;
|
||||||
|
`ExponentRemainderStepper.cs`, lines 49-72.)
|
||||||
|
2. **Bit-range skipping.** Because `Q_{k+1} / Q_k` is exactly the rational
|
||||||
|
`(2pk + 1)/(2p(k+1) + 1)`, we can predict when `⌊log₂ Q_k⌋` loses a bit and
|
||||||
|
jump the loop counter to the first `k` that crosses a target quotient bound
|
||||||
|
instead of checking each divisor. This is compatible with the existing
|
||||||
|
`allowedMax` guard because it only modifies how we arrive at the stopping
|
||||||
|
divisor. (`MersenneNumberDivisorByDivisorCpuTester.cs`, lines 161-266.)
|
||||||
|
3. **Reuse Montgomery metadata.** The loop already calls `MontgomeryDivisorDataCache.Get`
|
||||||
|
for each divisor, but the cache lookup is repeated even though the next
|
||||||
|
divisor is a small increment away. Persisting the previous `MontgomeryDivisorData`
|
||||||
|
together with the `(Q_k, r_k)` tuple lets us skip redundant cache reads when
|
||||||
|
the divisor stays within the hot range. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||||
|
lines 223-256.)
|
||||||
|
4. **Reciprocal-based delta evaluation.** `DivisorScanSession.TryComputeAnalyticHit` already keeps the `(Q_k, r_k)` tuple
|
||||||
|
per exponent, but the current update loop advances one step at a time and divides by `nextDivisor` through `Int128`
|
||||||
|
arithmetic. (`MersenneNumberDivisorByDivisorCpuTester.cs`, lines 531-594.) We can remove the per-iteration division by
|
||||||
|
storing a 64-bit reciprocal of `d_k` alongside the state and updating it with a single Newton step when the divisor grows
|
||||||
|
by `2p`. Once the reciprocal is known, `Δ_k` follows from a multiply-high of `2p·Q_k - r_k`. This turns the loop into a
|
||||||
|
straight-line sequence of multiplies and subtractions and makes the state update viable even when we unroll multiple
|
||||||
|
increments at once.
|
||||||
|
5. **Carry the Montgomery residue forward.** `PrimeDivisorState` already tracks `MontgomeryResidue`, yet the analytic path
|
||||||
|
currently recomputes it after the quotient update by calling `ToMontgomeryResidue`. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||||
|
lines 602-618.) We can instead reuse the residue by multiplying it with the precomputed `R^2 mod d_k` factor that the
|
||||||
|
cache returns for every divisor. Because the divisor grows deterministically, we only need to fetch the updated Montgomery
|
||||||
|
constants when `d_k` crosses a cache boundary, letting the fast path avoid modular reductions entirely.
|
||||||
|
|
||||||
|
These adjustments directly leverage the quotient progression we analysed and
|
||||||
|
align with the existing TODO list, giving us a clear path to reduce the number of
|
||||||
|
modular exponentiations and cache lookups in the CPU by-divisor path.
|
||||||
|
|
||||||
|
## Implementation status
|
||||||
|
|
||||||
|
* **Delta-driven Montgomery updates:** Implemented. The CPU session now keeps per-prime quotients, remainders, and last divisors so it can advance the analytic state across consecutive candidates, the reciprocal-corrected loop avoids per-step `UInt128` division, and zero cycle remainders short-circuit to direct Montgomery hits without invoking powmods.
|
||||||
|
* **Bit-range skipping:** Implemented. The CPU divisor sweep now predicts when the quotient loses a bit across both 64-bit and wider strides by deriving the next jump with `UInt128` arithmetic,
|
||||||
|
and it only falls back to single-step increments when a projected jump would overshoot the configured limit or exceed `ulong.MaxValue`.
|
||||||
|
* **Reuse Montgomery metadata:** Implemented. Each prime state now persists the divisor's `n'`, `R`, and `R^2` factors so resynchronization seeds the exponent stepper directly, and divisor sessions rebuild the Montgomery tuple from the cached state before touching the shared cache, meaning cache lookups only occur when no stored snapshot matches the new divisor.
|
||||||
|
* **Reciprocal-based delta evaluation:** Implemented. The analytic updater now retries with a direct 128-bit division when the reciprocal corrections exhaust their tolerance, refreshes the cached reciprocal from that exact quotient, and keeps the progression on the analytic path without rebuilding the state.
|
||||||
|
* **Carry the Montgomery residue forward:** Implemented. Each analytic update now normalizes the refreshed remainder and multiplies it by the cached `R^2 mod d` factor immediately, so the exponent stepper always has a Montgomery residue ready without recomputing modular reductions.
|
||||||
|
|
||||||
|
|
||||||
|
## Prime-order factoring bottleneck and planned fixes
|
||||||
|
|
||||||
|
The Pollard rho fallback in `PrimeOrderCalculator` still performs every quadratic
|
||||||
|
polynomial advance by issuing a full-width division. The tight loop in
|
||||||
|
`AdvancePolynomial` squares the iterate and takes a `% modulus`, which triggers
|
||||||
|
hardware division for each step and dominates the runtime once we test large
|
||||||
|
Mersenne divisors.【F:PerfectNumbers.Core/PrimeOrderCalculator.cs†L1180-L1211】
|
||||||
|
The by-divisor CPU path consequently spends a measurable portion of its wall
|
||||||
|
clock time inside the factorization helper whenever it needs to refresh a cycle
|
||||||
|
or validate that a divisor candidate is prime.
|
||||||
|
|
||||||
|
To bring this code in line with the Montgomery-first approach we use elsewhere
|
||||||
|
we should:
|
||||||
|
|
||||||
|
* **Introduce a Montgomery-backed polynomial step.** `PrimeDivisorState` already
|
||||||
|
carries the full set of Montgomery constants (`n'`, `R`, `R^2`) for each
|
||||||
|
divisor we touch in the by-divisor scan, and `DivisorScanSession` rebuilds a
|
||||||
|
`MontgomeryDivisorData` snapshot whenever it seeds the analytic stepper or the
|
||||||
|
exponent synchronizer.【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L645-L779】【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L1246-L1398】
|
||||||
|
We can surface the same data to the Pollard rho helper and rewrite
|
||||||
|
`AdvancePolynomial` so it accepts a precomputed reducer `(modulus, n', R^2)`.
|
||||||
|
The update then becomes `x = MontgomerySquareAdd(x, cMontgomery)`—a multiply
|
||||||
|
followed by one Montgomery reduction instead of a general-purpose remainder.
|
||||||
|
This eliminates the `% modulus` call entirely and lets the CPU factorizer reuse
|
||||||
|
the same fast path as the analytic delta updates.
|
||||||
|
* **Reuse reciprocal estimates between neighbouring divisors.** The analytic path
|
||||||
|
already stores a reciprocal for each divisor and refines it with Newton steps
|
||||||
|
to divide by `d_k` without hitting the 128-bit fallback.【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L876-L939】
|
||||||
|
Pollard rho can share the very same multiplier. We can stash the `FastDiv64`
|
||||||
|
multiplier inside the Pollard rho state and update it when `q` grows by `2p`
|
||||||
|
using a single Newton refinement, mirroring `DivideRoundUpUsingReciprocal`.
|
||||||
|
Once the reciprocal is in place, the `(x * x + c) mod q` step reduces to a
|
||||||
|
pair of `UInt128` products and one multiply-high to estimate the quotient—a
|
||||||
|
drop-in use of `ULongExtensions.FastDiv64`.【F:PerfectNumbers.Core/ULongExtensions.cs†L69-L101】
|
||||||
|
* **Warm-start the Montgomery context from the monotone `q` stream.** Each scan
|
||||||
|
step increments the divisor by the fixed stride `2p`, so the cached Montgomery
|
||||||
|
tuple only changes slowly. We can therefore hand the next divisor’s
|
||||||
|
`MontgomeryDivisorData` directly to the Pollard rho routine instead of forcing
|
||||||
|
it to recompute the reducer. This is already how `DivisorScanSession` avoids
|
||||||
|
redundant cache lookups; exporting the cached tuple keeps the factorizer in
|
||||||
|
lock-step with the rest of the pipeline.【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L645-L779】
|
||||||
|
|
||||||
|
With these changes the divisor-refresh path would no longer spend most of its
|
||||||
|
cycles on integer divisions, and the monotone growth of `q = 2kp + 1` for
|
||||||
|
`p ≥ 138,000,000` would guarantee that the reciprocal refinements stay stable
|
||||||
|
across thousands of consecutive candidates.
|
||||||
|
|
||||||
|
### Pollard rho factoring implementation status
|
||||||
|
|
||||||
|
* **Montgomery-backed polynomial step:** Completed. `PollardRhoMontgomeryReducer` now squares and adds in Montgomery space,
|
||||||
|
and the Pollard rho loops in `PrimeOrderCalculator` and `MersenneDivisorCycles` reuse the shared reducer instead of issuing `%` operations.
|
||||||
|
* **Reciprocal reuse:** Completed. The reducer tracks a cached FastDiv64 reciprocal, refines it for each monotone modulus, and exposes `AdvanceStandard` so the standard-domain iterates reuse the cached multiplier without `%` or `UInt128` division.
|
||||||
|
* **Warm-started Montgomery context:** Completed. The reducer accepts precomputed `MontgomeryDivisorData` snapshots, letting callers seed Pollard rho directly from the cached divisor state when the by-divisor pipeline advances to the next `q`.
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@ PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207:
|
|||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:283: // TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||||
PerfectNumbers.Core\IMersenneNumberDivisorByDivisorTester.cs:7: bool UseDivisorCycles { get; set; } // TODO: Remove the setter once divisor cycle usage becomes mandatory so all implementations always leverage the cached cycles.
|
PerfectNumbers.Core\IMersenneNumberDivisorByDivisorTester.cs:7: bool UseDivisorCycles { get; set; } // TODO: Remove the setter once divisor cycle usage becomes mandatory so all implementations always leverage the cached cycles.
|
||||||
@@ -267,7 +266,6 @@ todo_list.txt:122:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTeste
|
|||||||
todo_list.txt:123:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
todo_list.txt:123:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
||||||
todo_list.txt:124:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
todo_list.txt:124:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||||
todo_list.txt:125:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
todo_list.txt:125:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||||
todo_list.txt:126:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:283: // TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
|
||||||
todo_list.txt:127:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
todo_list.txt:127:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
||||||
todo_list.txt:128:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
todo_list.txt:128:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||||
todo_list.txt:129:PerfectNumbers.Core\IMersenneNumberDivisorByDivisorTester.cs:7: bool UseDivisorCycles { get; set; } // TODO: Remove the setter once divisor cycle usage becomes mandatory so all implementations always leverage the cached cycles.
|
todo_list.txt:129:PerfectNumbers.Core\IMersenneNumberDivisorByDivisorTester.cs:7: bool UseDivisorCycles { get; set; } // TODO: Remove the setter once divisor cycle usage becomes mandatory so all implementations always leverage the cached cycles.
|
||||||
|
|||||||
Reference in new issue
Block a user