Compare commits

...
29 changed files with 1466 additions and 984 deletions

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;
@@ -22,320 +22,391 @@ namespace EvenPerfectBitScanner.Benchmarks;
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)] [SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
public class Pow2MontgomeryModCycleComputationBenchmarks public class Pow2MontgomeryModCycleComputationBenchmarks
{ {
private const int SampleCount = 256; private const int SampleCount = 256;
private const ulong OneShiftedLeft63 = 1UL << 63; private const ulong OneShiftedLeft63 = 1UL << 63;
private const long OneShiftedLeft60 = 1L << 60; private const long OneShiftedLeft60 = 1L << 60;
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount]; private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount]; private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
private readonly MontgomeryDivisorData[] _veryLargeDivisors = new MontgomeryDivisorData[SampleCount]; private readonly MontgomeryDivisorData[] _veryLargeDivisors = new MontgomeryDivisorData[SampleCount];
private readonly UInt128[] _wideModuli = new UInt128[SampleCount]; private readonly UInt128[] _wideModuli = new UInt128[SampleCount];
private readonly ulong[] _smallExponents = new ulong[SampleCount]; private readonly ulong[] _smallExponents = new ulong[SampleCount];
private readonly ulong[] _largeExponents = new ulong[SampleCount]; private readonly ulong[] _largeExponents = new ulong[SampleCount];
private readonly ulong[] _veryLargeExponents = new ulong[SampleCount]; private readonly ulong[] _veryLargeExponents = new ulong[SampleCount];
private readonly ulong[] _smallCycles = new ulong[SampleCount]; private readonly ulong[] _smallCycles = new ulong[SampleCount];
private readonly ulong[] _largeCycles = new ulong[SampleCount]; private readonly ulong[] _largeCycles = new ulong[SampleCount];
private readonly ulong[] _veryLargeCycles = new ulong[SampleCount]; private readonly ulong[] _veryLargeCycles = new ulong[SampleCount];
private readonly UInt128[] _wideCycles = new UInt128[SampleCount]; private readonly UInt128[] _wideCycles = new UInt128[SampleCount];
private readonly Random _random = new(113); private readonly Random _random = new(113);
private ulong? _previousPrimeOrder; private ulong? _previousPrimeOrder;
private UInt128? _previousWidePrimeOrder; private UInt128? _previousWidePrimeOrder;
public enum InputScale public enum InputScale
{ {
Small, Small,
Large, Large,
VeryLarge VeryLarge
} }
[ParamsAllValues] [ParamsAllValues]
public InputScale Scale { get; set; } public InputScale Scale { get; set; }
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
for (int i = 0; i < SampleCount; i++) for (int i = 0; i < SampleCount; i++)
{ {
#if DEBUG #if DEBUG
Console.WriteLine($"Generating sample {i}"); Console.WriteLine($"Generating sample {i}");
#endif #endif
ulong smallModulus = NextSmallOddModulus(); ulong smallModulus = NextSmallOddModulus();
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus); _smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
_smallExponents[i] = NextSmallExponent(); _smallExponents[i] = NextSmallExponent();
_smallCycles[i] = CalculateCycleLengthWithHeuristics(smallModulus); _smallCycles[i] = CalculateCycleLengthWithHeuristics(smallModulus);
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle(); (ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus); _largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
_largeExponents[i] = NextLargeExponent(); _largeExponents[i] = NextLargeExponent();
_largeCycles[i] = CalculateCycleLengthWithHeuristics(largeModulus, largeCycle); _largeCycles[i] = CalculateCycleLengthWithHeuristics(largeModulus, largeCycle);
ulong veryLargeModulus = NextVeryLargeOddModulus(); ulong veryLargeModulus = NextVeryLargeOddModulus();
_veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus); _veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus);
_veryLargeExponents[i] = NextVeryLargeExponent(); _veryLargeExponents[i] = NextVeryLargeExponent();
#if DEBUG #if DEBUG
Console.WriteLine($"Calculating cycle length {veryLargeModulus}"); Console.WriteLine($"Calculating cycle length {veryLargeModulus}");
#endif #endif
_veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus); _veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus);
Console.WriteLine($"Generating wide sample {i}"); Console.WriteLine($"Generating wide sample {i}");
UInt128 wideModulus = NextWideOddModulus(); UInt128 wideModulus = NextWideOddModulus();
_wideModuli[i] = wideModulus; _wideModuli[i] = wideModulus;
_wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus); _wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus);
} }
} }
/// <summary> /// <summary>
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the /// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
/// 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; return checksum;
} }
/// <summary> /// <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 /// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
/// on the large one (0.08×). /// large set.
/// </summary> /// </summary>
[Benchmark] // [Benchmark(Baseline = true)]
public ulong MontgomeryWithPrecomputedCycle() public ulong MontgomeryWithoutCycleGpu()
{ {
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles); 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].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]); checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
} }
return checksum; return checksum;
} }
/// <summary> /// <summary>
/// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to /// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline). /// on the large one (0.08×).
/// </summary> /// </summary>
// [Benchmark] [Benchmark]
public ulong MontgomeryWithGpuCycleComputation() public ulong MontgomeryWithPrecomputedCycleCpu()
{ {
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _); 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++)
{ {
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus); checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]); }
}
return checksum; 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;
}
/// <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 MontgomeryWithGpuCycleComputationCpu()
{
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].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
}
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]
public ulong MontgomeryWithHeuristicCycleComputationCpu()
{
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].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;
}
[Benchmark]
public UInt128 HeuristicWideCycleComputation()
{
UInt128 checksum = UInt128.Zero;
for (int i = 0; i < SampleCount; i++)
{
UInt128 cycle = CalculateCycleLengthWithHeuristics(_wideModuli[i]);
_wideCycles[i] = cycle;
checksum ^= cycle;
}
return checksum;
}
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
{
switch (Scale)
{
case InputScale.Small:
exponents = _smallExponents;
divisors = _smallDivisors;
cycles = _smallCycles;
break;
case InputScale.Large:
exponents = _largeExponents;
divisors = _largeDivisors;
cycles = _largeCycles;
break;
case InputScale.VeryLarge:
exponents = _veryLargeExponents;
divisors = _veryLargeDivisors;
cycles = _veryLargeCycles;
break;
default:
throw new ArgumentOutOfRangeException(nameof(Scale), Scale, null);
}
}
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
private ulong NextLargeExponent() => (ulong)_random.NextInt64(OneShiftedLeft60, long.MaxValue);
private ulong NextVeryLargeExponent()
{
Span<byte> buffer = stackalloc byte[8];
_random.NextBytes(buffer);
ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63);
return value;
}
private ulong NextSmallOddModulus()
{
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
return value | 1UL;
}
private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle()
{
int bitLength = _random.Next(48, 64);
ulong modulus = (1UL << bitLength) - 1UL;
return (modulus, (ulong)bitLength);
}
private ulong NextVeryLargeOddModulus()
{
Span<byte> buffer = stackalloc byte[8];
_random.NextBytes(buffer);
ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL;
return value;
}
private UInt128 NextWideOddModulus()
{
Span<byte> buffer = stackalloc byte[16];
_random.NextBytes(buffer);
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
Span<byte> highSlice = buffer.Slice(8, 8);
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(highSlice) | (1UL << 63);
UInt128 value = ((UInt128)high << 64) | low;
return value | UInt128.One;
}
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
}
ulong nPrime = ComputeMontgomeryNPrime(modulus);
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
return new MontgomeryDivisorData(
modulus,
nPrime,
montgomeryOne,
montgomeryTwo,
montgomeryTwoSquared);
}
[Benchmark] private ulong CalculateCycleLengthWithHeuristics(ulong modulus, ulong fallbackOrder = 0UL)
public ulong MontgomeryWithHeuristicCycleComputation() {
{ MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _); if (modulus <= 1UL || (modulus & 1UL) == 0UL)
ulong checksum = 0UL; {
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
}
for (int i = 0; i < SampleCount; i++) // When the below is uncommented, the benchmark never completes due to the primality test cost on very large random set.
{
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
}
return checksum; // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(modulus);
} // // bool isPrime = PrimeTester.IsPrimeInternal(modulus, CancellationToken.None);
// if (!isPrime)
[Benchmark] // {
public UInt128 HeuristicWideCycleComputation() // return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
{ // }
UInt128 checksum = UInt128.Zero;
for (int i = 0; i < SampleCount; i++)
{
UInt128 cycle = CalculateCycleLengthWithHeuristics(_wideModuli[i]);
_wideCycles[i] = cycle;
checksum ^= cycle;
}
return checksum;
}
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
{
switch (Scale)
{
case InputScale.Small:
exponents = _smallExponents;
divisors = _smallDivisors;
cycles = _smallCycles;
break;
case InputScale.Large:
exponents = _largeExponents;
divisors = _largeDivisors;
cycles = _largeCycles;
break;
case InputScale.VeryLarge:
exponents = _veryLargeExponents;
divisors = _veryLargeDivisors;
cycles = _veryLargeCycles;
break;
default:
throw new ArgumentOutOfRangeException(nameof(Scale), Scale, null);
}
}
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
private ulong NextLargeExponent() => (ulong)_random.NextInt64(OneShiftedLeft60, long.MaxValue);
private ulong NextVeryLargeExponent()
{
Span<byte> buffer = stackalloc byte[8];
_random.NextBytes(buffer);
ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63);
return value;
}
private ulong NextSmallOddModulus()
{
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
return value | 1UL;
}
private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle()
{
int bitLength = _random.Next(48, 64);
ulong modulus = (1UL << bitLength) - 1UL;
return (modulus, (ulong)bitLength);
}
private ulong NextVeryLargeOddModulus()
{
Span<byte> buffer = stackalloc byte[8];
_random.NextBytes(buffer);
ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL;
return value;
}
private UInt128 NextWideOddModulus()
{
Span<byte> buffer = stackalloc byte[16];
_random.NextBytes(buffer);
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
Span<byte> highSlice = buffer.Slice(8, 8);
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(highSlice) | (1UL << 63);
UInt128 value = ((UInt128)high << 64) | low;
return value | UInt128.One;
}
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
}
ulong nPrime = ComputeMontgomeryNPrime(modulus);
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
return new MontgomeryDivisorData(
modulus,
nPrime,
montgomeryOne,
montgomeryTwo,
montgomeryTwoSquared);
}
private ulong CalculateCycleLengthWithHeuristics(ulong modulus, ulong fallbackOrder = 0UL)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
}
// When the below is uncommented, the benchmark never completes due to the primality test cost on very large random set.
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(modulus);
// // bool isPrime = PrimeTester.IsPrimeInternal(modulus, CancellationToken.None);
// if (!isPrime)
// {
// return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
// }
#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;
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation"); Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData); return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
} }
private UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus) private UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus)
{ {
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero) if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
{ {
return MersenneDivisorCycles.GetCycle(modulus); return MersenneDivisorCycles.GetCycle(modulus);
} }
#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,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
if (orderResult.Order != UInt128.Zero) if (order != UInt128.Zero)
{ {
_previousWidePrimeOrder = orderResult.Order; _previousWidePrimeOrder = order;
return orderResult.Order; return order;
} }
_previousWidePrimeOrder = null; _previousWidePrimeOrder = null;
Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation"); Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation");
return MersenneDivisorCycles.GetCycle(modulus); return MersenneDivisorCycles.GetCycle(modulus);
} }
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus); private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
private static ulong ComputeMontgomeryNPrime(ulong modulus) private static ulong ComputeMontgomeryNPrime(ulong modulus)
{ {
ulong inv = modulus; ulong inv = modulus;
inv *= unchecked(2UL - modulus * inv); inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv); inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv); inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv); inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv); inv *= unchecked(2UL - modulus * inv);
inv *= unchecked(2UL - modulus * inv); inv *= unchecked(2UL - modulus * inv);
return unchecked(0UL - inv); return unchecked(0UL - inv);
} }
} }
@@ -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)
{ {
@@ -15,9 +15,15 @@ public class GpuContextPoolTests
{ {
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
using var lease = GpuContextPool.RentPreferred(preferCpu: true); GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU); {
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
}
finally
{
lease.Dispose();
}
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
} }
@@ -30,17 +36,27 @@ public class GpuContextPoolTests
Context firstContext; Context firstContext;
Accelerator firstAccelerator; Accelerator firstAccelerator;
using (var lease = GpuContextPool.RentPreferred(preferCpu: true)) GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try
{ {
firstContext = lease.Context; firstContext = lease.Context;
firstAccelerator = lease.Accelerator; firstAccelerator = lease.Accelerator;
} }
finally
{
lease.Dispose();
}
using (var lease = GpuContextPool.RentPreferred(preferCpu: true)) lease = GpuContextPool.RentPreferred(preferCpu: true);
try
{ {
lease.Context.Should().BeSameAs(firstContext); lease.Context.Should().BeSameAs(firstContext);
lease.Accelerator.Should().BeSameAs(firstAccelerator); lease.Accelerator.Should().BeSameAs(firstAccelerator);
} }
finally
{
lease.Dispose();
}
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
} }
@@ -52,17 +68,27 @@ public class GpuContextPoolTests
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
Context firstContext; Context firstContext;
using (var lease = GpuContextPool.RentPreferred(preferCpu: true)) GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try
{ {
firstContext = lease.Context; firstContext = lease.Context;
} }
finally
{
lease.Dispose();
}
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
using (var lease = GpuContextPool.RentPreferred(preferCpu: true)) lease = GpuContextPool.RentPreferred(preferCpu: true);
try
{ {
lease.Context.Should().NotBeSameAs(firstContext); lease.Context.Should().NotBeSameAs(firstContext);
} }
finally
{
lease.Dispose();
}
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
} }
@@ -199,10 +199,17 @@ public class GpuKernelCompilationTests
[MemberData(nameof(KernelLoaders))] [MemberData(nameof(KernelLoaders))]
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile) public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
{ {
using var lease = GpuContextPool.RentPreferred(preferCpu: true); GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
var accelerator = lease.Accelerator; try
Action action = () => compile(accelerator); {
action.Should().NotThrow($"kernel {name} should compile"); var accelerator = lease.Accelerator;
Action action = () => compile(accelerator);
action.Should().NotThrow($"kernel {name} should compile");
}
finally
{
lease.Dispose();
}
} }
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator) private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
@@ -150,29 +150,36 @@ public class NttGpuMathTests
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
using var gpu = GpuContextPool.Rent(); GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator; try
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull();
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
var cacheType = cache.GetType();
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
int count = 7; // length 8 -> 7 twiddles
var forward = new GpuUInt128[count];
var inverse = new GpuUInt128[count];
twiddlesMont.CopyToCPU(forward);
twiddlesInvMont.CopyToCPU(inverse);
for (int i = 0; i < count; i++)
{ {
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod); var accelerator = gpu.Accelerator;
prod.Should().Be(r2);
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull();
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
var cacheType = cache.GetType();
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
int count = 7; // length 8 -> 7 twiddles
var forward = new GpuUInt128[count];
var inverse = new GpuUInt128[count];
twiddlesMont.CopyToCPU(forward);
twiddlesInvMont.CopyToCPU(inverse);
for (int i = 0; i < count; i++)
{
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
prod.Should().Be(r2);
}
}
finally
{
gpu.Dispose();
} }
} }
@@ -338,12 +345,23 @@ public class NttGpuMathTests
values[1] = new GpuUInt128(0UL, 2UL); values[1] = new GpuUInt128(0UL, 2UL);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
using (var gpu = GpuContextPool.Rent()) GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
try
{ {
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length); var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
buffer.View.CopyFromCPU(ref values[0], values.Length); try
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot); {
buffer.Dispose(); buffer.View.CopyFromCPU(ref values[0], values.Length);
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
}
finally
{
buffer.Dispose();
}
}
finally
{
gpu.Dispose();
} }
// Verify that the square cache now holds entries for the accelerator. // Verify that the square cache now holds entries for the accelerator.
@@ -33,16 +33,23 @@ public class NttMontgomeryTests
NttGpuMath.Square(cpu, modulus, primitiveRoot); NttGpuMath.Square(cpu, modulus, primitiveRoot);
// Device execution // Device execution
using var context = GpuContextPool.Rent(); GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
var accelerator = context.Accelerator; try
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
for (int i = 0; i < cpu.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); var accelerator = context.Accelerator;
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
for (int i = 0; i < cpu.Length; i++)
{
gpu[i].Should().Be(cpu[i]);
}
}
finally
{
context.Dispose();
} }
} }
@@ -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
{ {
@@ -11,14 +11,21 @@ public class GpuKernelPoolTests
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls() public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
{ {
using var lease = GpuContextPool.RentPreferred(preferCpu: true); GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
var accelerator = lease.Accelerator; try
{
var accelerator = lease.Accelerator;
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
second.Equals(first).Should().BeTrue(); second.Equals(first).Should().BeTrue();
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1); second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
}
finally
{
lease.Dispose();
}
} }
[Fact] [Fact]
@@ -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,13 @@ 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,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found); result.Should().Be((UInt128)1229782938247309446UL);
result.Order.Should().Be((UInt128)1229782938247309446UL);
} }
[Fact] [Fact]
@@ -238,20 +238,19 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
} }
if (divisorCycle != 0 && divisorCycle == prime) if (divisorCycle == prime)
{ {
processedAll = true; // A cycle equal to the tested exponent (which is prime in this path)
return true; // guarantees that the candidate divides the corresponding Mersenne
} // number because the order of 2 modulo the divisor is exactly p.
else if (divisorCycle == 0) processedAll = true;
{ return true;
Console.WriteLine($"Divisor cycle wasn't calculated for ${prime}"); }
}
// if (divisorCycle != 0UL && CheckDivisor(prime, divisorCycle, divisorData) != 0) if (divisorCycle == 0UL)
// { {
// processedAll = true; Console.WriteLine($"Divisor cycle was not calculated for {prime}");
// return true; }
// }
} }
divisor += step; divisor += step;
@@ -269,7 +268,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData) private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
{ {
ulong residue = prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData); ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
return residue == 1UL ? (byte)1 : (byte)0; return residue == 1UL ? (byte)1 : (byte)0;
} }
@@ -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;
}
}
@@ -47,7 +47,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 +59,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 +77,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 +88,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 +123,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;
+8
View File
@@ -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;
}
}
+1 -1
View File
@@ -126,7 +126,7 @@ public static class GpuContextPool
} }
} }
public struct GpuContextLease : IDisposable public struct GpuContextLease
{ {
private readonly PooledContext _ctx; private readonly PooledContext _ctx;
private bool disposed; private bool disposed;
+1 -1
View File
@@ -677,7 +677,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
// GC.SuppressFinalize(this); // GC.SuppressFinalize(this);
} }
public readonly struct ExecutionScope : IDisposable public readonly struct ExecutionScope
{ {
private readonly object? _lock; private readonly object? _lock;
+12 -12
View File
@@ -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)
+12 -12
View File
@@ -102,7 +102,7 @@ public class MersenneDivisorCycles
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 CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
@@ -126,7 +126,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 +234,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
@@ -285,15 +285,15 @@ public class MersenneDivisorCycles
return true; return true;
} }
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate( ulong computedOrder = 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 (computedOrder != 0UL)
{ {
cycleLength = orderResult.Order; cycleLength = computedOrder;
return true; return true;
} }
@@ -505,7 +505,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;
@@ -940,15 +940,15 @@ public class MersenneDivisorCycles
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None)) if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
{ {
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate( ulong computedOrder = 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 (computedOrder != 0UL)
{ {
cycleLength = orderResult.Order; cycleLength = computedOrder;
return true; return true;
} }
} }
@@ -112,9 +112,12 @@ public static class MersenneNumberDivisorByDivisorTester
ulong maxPrime = 0UL; ulong maxPrime = 0UL;
int primeWriteIndex = 0; int primeWriteIndex = 0;
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator(); // This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
bool hasPrime = primeEnumerator.MoveNext(); // We don't need to additionally check it.
ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
// using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
// bool hasPrime = primeEnumerator.MoveNext();
// ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
for (int index = startIndex; index < candidateCount; index++) for (int index = startIndex; index < candidateCount; index++)
{ {
@@ -127,25 +130,29 @@ public static class MersenneNumberDivisorByDivisorTester
continue; continue;
} }
while (hasPrime && currentPrime < candidate) // This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
{ // We don't need to additionally check it.
hasPrime = primeEnumerator.MoveNext();
if (!hasPrime)
{
break;
}
currentPrime = primeEnumerator.Current; // while (hasPrime && currentPrime < candidate)
} // {
// hasPrime = primeEnumerator.MoveNext();
// if (!hasPrime)
// {
// break;
// }
if (!hasPrime || currentPrime != candidate) // currentPrime = primeEnumerator.Current;
{ // }
markComposite();
printResult(candidate, false, false, false);
continue;
}
primesSpan[primeWriteIndex++] = candidate; // if (!hasPrime || currentPrime != candidate)
// if (!Number.IsPrime(candidate))
// {
// markComposite();
// printResult(candidate, false, false, false);
// continue;
// }
primesSpan[primeWriteIndex++] = candidate;
if (candidate > maxPrime) if (candidate > maxPrime)
{ {
@@ -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,81 @@
using System;
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 PartialFactorResult();
}
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 PartialFactorResult
{
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
@@ -9,30 +9,15 @@ namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator internal static partial class PrimeOrderCalculator
{ {
internal readonly struct PrimeOrderResultWide public static UInt128 Calculate(
{
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);
public static PrimeOrderResultWide 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 +36,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 +88,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 +96,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)
File diff suppressed because it is too large. Load diff
+6 -8
View File
@@ -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)]
+117 -8
View File
@@ -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;
@@ -76,7 +77,7 @@ public static class ULongExtensions
public static ulong MultiplyShiftRightShiftFirst(ulong value, ulong multiplier, int shift) public static ulong MultiplyShiftRightShiftFirst(ulong value, ulong multiplier, int shift)
{ {
ulong high = value >> shift; ulong high = value >> shift;
ulong mask = (1UL << shift) - 1UL; ulong mask = (1UL << shift) - 1UL;
ulong low = value & mask; ulong low = value & mask;
UInt128 highContribution = (UInt128)high * multiplier; UInt128 highContribution = (UInt128)high * multiplier;
@@ -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)
{ {