PrimeOrderCalculatorAccelerator.Rent is now created outside of the loops and re-used for all calculations.

This commit is contained in:
mlemanczyk committed 2025-11-24 22:51:53 +01:00
1 parent 57a50e2a37
commit b9ca95bf8c
53 files changed
+4636 -4226

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -2,6 +2,7 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System.Numerics;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace EvenPerfectBitScanner.Benchmarks;
@@ -9,43 +10,46 @@ namespace EvenPerfectBitScanner.Benchmarks;
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
public class Pow2MontgomeryModBenchmarks
{
private const int SampleCount = 256;
private const int SampleCount = 256;
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
private readonly ulong[] _smallExponents = new ulong[SampleCount];
private readonly ulong[] _largeExponents = new ulong[SampleCount];
private readonly ulong[] _smallCycles = new ulong[SampleCount];
private readonly ulong[] _largeCycles = new ulong[SampleCount];
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
private readonly ulong[] _smallExponents = new ulong[SampleCount];
private readonly ulong[] _largeExponents = new ulong[SampleCount];
private readonly ulong[] _smallCycles = new ulong[SampleCount];
private readonly ulong[] _largeCycles = new ulong[SampleCount];
private readonly Random _random = new(13);
private readonly Random _random = new(13);
public enum InputScale
{
Small,
Large
}
public enum InputScale
{
Small,
Large
}
[ParamsAllValues]
public InputScale Scale { get; set; }
[ParamsAllValues]
public InputScale Scale { get; set; }
[GlobalSetup]
public void Setup()
{
for (int i = 0; i < SampleCount; i++)
{
ulong smallModulus = NextSmallOddModulus();
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
_smallExponents[i] = NextSmallExponent();
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus));
[GlobalSetup]
public void Setup()
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < SampleCount; i++)
{
ulong smallModulus = NextSmallOddModulus();
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
_smallExponents[i] = NextSmallExponent();
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(gpu, smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus));
Console.WriteLine($"Calculating large cycle {i + 1} with heuristics");
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
Console.WriteLine($"Calculating large cycle {i + 1} with heuristics");
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
_largeExponents[i] = NextLargeExponent();
_largeCycles[i] = largeCycle;
}
}
_largeExponents[i] = NextLargeExponent();
_largeCycles[i] = largeCycle;
}
PrimeOrderCalculatorAccelerator.Return(gpu);
}
/// <summary>
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
@@ -64,455 +68,459 @@ public class Pow2MontgomeryModBenchmarks
return checksum;
}
/// <summary>
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
/// <summary>
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
///
/// This benchmark failed running, hanging up early before reaching workload testing.
/// </summary>
// [Benchmark]
public ulong BaselineGpu()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
/// </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].Pow2MontgomeryModWindowedConvertGpu(divisors[i]);
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(gpu, divisors[i]);
}
return checksum;
}
PrimeOrderCalculatorAccelerator.Return(gpu);
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 BaselineWithKnownCycleCpu()
{
GetData(out var exponents, out var divisors, out var cycles);
ulong checksum = 0UL;
/// <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 BaselineWithKnownCycleCpu()
{
GetData(out var exponents, out var divisors, out var cycles);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
}
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
}
return checksum;
}
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;
/// <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]);
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycles[i], divisors[i]);
}
return checksum;
}
PrimeOrderCalculatorAccelerator.Return(gpu);
return checksum;
}
/// <summary>
/// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×).
/// </summary>
[Benchmark]
public ulong LeftToRight()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
/// <summary>
/// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×).
/// </summary>
[Benchmark]
public ulong LeftToRight()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]);
}
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]);
}
return checksum;
}
return checksum;
}
/// <summary>
/// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×).
/// </summary>
[Benchmark]
public ulong Batched4()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
/// <summary>
/// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×).
/// </summary>
[Benchmark]
public ulong Batched4()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]);
}
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]);
}
return checksum;
}
return checksum;
}
/// <summary>
/// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×).
/// </summary>
[Benchmark]
public ulong SlidingWindow()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
/// <summary>
/// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×).
/// </summary>
[Benchmark]
public ulong SlidingWindow()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]);
}
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]);
}
return checksum;
}
return checksum;
}
/// <summary>
/// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×).
/// </summary>
[Benchmark]
public ulong BinaryMod()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
/// <summary>
/// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×).
/// </summary>
[Benchmark]
public ulong BinaryMod()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus);
}
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus);
}
return checksum;
}
return checksum;
}
/// <summary>
/// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×).
/// </summary>
[Benchmark]
public ulong PrecomputedTableMod()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
/// <summary>
/// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×).
/// </summary>
[Benchmark]
public ulong PrecomputedTableMod()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus);
}
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus);
}
return checksum;
}
return checksum;
}
/// <summary>
/// Relies on <see cref="BigInteger.ModPow"/>; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×).
/// </summary>
[Benchmark]
public ulong BigIntegerMod()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
/// <summary>
/// Relies on <see cref="BigInteger.ModPow"/>; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×).
/// </summary>
[Benchmark]
public ulong BigIntegerMod()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus);
}
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus);
}
return checksum;
}
return checksum;
}
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
{
if (Scale == InputScale.Small)
{
exponents = _smallExponents;
divisors = _smallDivisors;
cycles = _smallCycles;
}
else
{
exponents = _largeExponents;
divisors = _largeDivisors;
cycles = _largeCycles;
}
}
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
{
if (Scale == InputScale.Small)
{
exponents = _smallExponents;
divisors = _smallDivisors;
cycles = _smallCycles;
}
else
{
exponents = _largeExponents;
divisors = _largeDivisors;
cycles = _largeCycles;
}
}
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
private ulong NextLargeExponent() => (ulong)_random.NextInt64(1L << 60, long.MaxValue);
private ulong NextLargeExponent() => (ulong)_random.NextInt64(1L << 60, long.MaxValue);
private ulong NextSmallOddModulus()
{
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
return value | 1UL;
}
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 modulus, ulong cycleLength) NextLargeModulusAndCycle()
{
int bitLength = _random.Next(48, 64);
ulong modulus = (1UL << bitLength) - 1UL;
return (modulus, (ulong)bitLength);
}
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
}
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.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
ulong nPrime = ComputeMontgomeryNPrime(modulus);
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
return new MontgomeryDivisorData(
modulus,
nPrime,
montgomeryOne,
montgomeryTwo,
montgomeryTwoSquared);
}
return new MontgomeryDivisorData(
modulus,
nPrime,
montgomeryOne,
montgomeryTwo,
montgomeryTwoSquared);
}
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)
{
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);
return unchecked(0UL - inv);
}
private static ulong ComputeMontgomeryNPrime(ulong 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);
return unchecked(0UL - inv);
}
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor)
{
ulong modulus = divisor.Modulus;
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor)
{
ulong modulus = divisor.Modulus;
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
ulong result = divisor.MontgomeryOne;
ulong baseVal = divisor.MontgomeryTwo;
ulong nPrime = divisor.NPrime;
ulong result = divisor.MontgomeryOne;
ulong baseVal = divisor.MontgomeryTwo;
ulong nPrime = divisor.NPrime;
if (exponent == 0UL)
{
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
if (exponent == 0UL)
{
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
{
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
{
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
if (((exponent >> bitIndex) & 1UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
}
}
if (((exponent >> bitIndex) & 1UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
}
}
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
{
ulong modulus = divisor.Modulus;
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
{
ulong modulus = divisor.Modulus;
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
ulong result = divisor.MontgomeryOne;
ulong baseVal = divisor.MontgomeryTwo;
ulong nPrime = divisor.NPrime;
ulong remainingExponent = exponent;
ulong result = divisor.MontgomeryOne;
ulong baseVal = divisor.MontgomeryTwo;
ulong nPrime = divisor.NPrime;
ulong remainingExponent = exponent;
while (remainingExponent != 0UL)
{
ulong nibble = remainingExponent & 0xFUL;
while (remainingExponent != 0UL)
{
ulong nibble = remainingExponent & 0xFUL;
ulong base0 = baseVal;
ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime);
ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime);
ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime);
ulong base0 = baseVal;
ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime);
ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime);
ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime);
if ((nibble & 1UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
}
if ((nibble & 1UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
}
if ((nibble & 2UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
}
if ((nibble & 2UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
}
if ((nibble & 4UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
}
if ((nibble & 4UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
}
if ((nibble & 8UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
}
if ((nibble & 8UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
}
baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
remainingExponent >>= 4;
}
baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
remainingExponent >>= 4;
}
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
// TODO: Update it with the latest implementations
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
{
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
{
ulong modulus = divisor.Modulus;
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
ulong nPrime = divisor.NPrime;
ulong result = divisor.MontgomeryOne;
ulong baseVal = divisor.MontgomeryTwo;
ulong nPrime = divisor.NPrime;
ulong result = divisor.MontgomeryOne;
ulong baseVal = divisor.MontgomeryTwo;
if (exponent == 0UL)
{
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
if (exponent == 0UL)
{
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
const int WindowSize = 5;
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
oddPowers[0] = baseVal;
ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
for (int i = 1; i < oddPowers.Length; i++)
{
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime);
}
const int WindowSize = 5;
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
oddPowers[0] = baseVal;
ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
for (int i = 1; i < oddPowers.Length; i++)
{
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime);
}
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
while (bitIndex >= 0)
{
if (((exponent >> bitIndex) & 1UL) == 0UL)
{
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
bitIndex--;
continue;
}
while (bitIndex >= 0)
{
if (((exponent >> bitIndex) & 1UL) == 0UL)
{
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
bitIndex--;
continue;
}
int windowStart = Math.Max(bitIndex - WindowSize + 1, 0);
ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL);
int windowStart = Math.Max(bitIndex - WindowSize + 1, 0);
ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL);
while ((windowValue & 1UL) == 0UL)
{
windowValue >>= 1;
windowStart++;
}
while ((windowValue & 1UL) == 0UL)
{
windowValue >>= 1;
windowStart++;
}
int squares = bitIndex - windowStart + 1;
for (int i = 0; i < squares; i++)
{
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
}
int squares = bitIndex - windowStart + 1;
for (int i = 0; i < squares; i++)
{
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
}
result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
bitIndex = windowStart - 1;
}
result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
bitIndex = windowStart - 1;
}
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
// TODO: Update it with the latest implementations, if it exists in production code. Otherwise just remove TODO
private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
ulong result = 1UL % modulus;
ulong baseVal = 2UL % modulus;
ulong remainingExponent = exponent;
ulong result = 1UL % modulus;
ulong baseVal = 2UL % modulus;
ulong remainingExponent = exponent;
while (remainingExponent > 0UL)
{
if ((remainingExponent & 1UL) != 0UL)
{
result = MultiplyMod(result, baseVal, modulus);
}
while (remainingExponent > 0UL)
{
if ((remainingExponent & 1UL) != 0UL)
{
result = MultiplyMod(result, baseVal, modulus);
}
remainingExponent >>= 1;
if (remainingExponent == 0UL)
{
break;
}
remainingExponent >>= 1;
if (remainingExponent == 0UL)
{
break;
}
baseVal = MultiplyMod(baseVal, baseVal, modulus);
}
baseVal = MultiplyMod(baseVal, baseVal, modulus);
}
return result;
}
return result;
}
private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
if (exponent == 0UL)
{
return 1UL % modulus;
}
if (exponent == 0UL)
{
return 1UL % modulus;
}
Span<ulong> powers = stackalloc ulong[64];
powers[0] = 2UL % modulus;
int highestBit = 63 - BitOperations.LeadingZeroCount(exponent);
Span<ulong> powers = stackalloc ulong[64];
powers[0] = 2UL % modulus;
int highestBit = 63 - BitOperations.LeadingZeroCount(exponent);
for (int index = 1; index <= highestBit; index++)
{
powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus);
}
for (int index = 1; index <= highestBit; index++)
{
powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus);
}
ulong result = 1UL % modulus;
ulong remainingExponent = exponent;
int bitIndex = 0;
ulong result = 1UL % modulus;
ulong remainingExponent = exponent;
int bitIndex = 0;
while (remainingExponent > 0UL)
{
if ((remainingExponent & 1UL) != 0UL)
{
result = MultiplyMod(result, powers[bitIndex], modulus);
}
while (remainingExponent > 0UL)
{
if ((remainingExponent & 1UL) != 0UL)
{
result = MultiplyMod(result, powers[bitIndex], modulus);
}
remainingExponent >>= 1;
bitIndex++;
}
remainingExponent >>= 1;
bitIndex++;
}
return result;
}
return result;
}
private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
BigInteger mod = modulus;
BigInteger result = BigInteger.ModPow(2, exponent, mod);
return (ulong)result;
}
BigInteger mod = modulus;
BigInteger result = BigInteger.ModPow(2, exponent, mod);
return (ulong)result;
}
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
{
UInt128 product = (UInt128)x * y;
return (ulong)(product % modulus);
}
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
{
UInt128 product = (UInt128)x * y;
return (ulong)(product % modulus);
}
}
@@ -1,4 +1,5 @@
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace EvenPerfectBitScanner.Benchmarks;
@@ -60,12 +61,13 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
};
var cases = new Pow2MontgomeryModWindowedBenchmarkCase[seeds.Length];
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < seeds.Length; i++)
{
Pow2MontgomeryModWindowedBenchmarkSeed seed = seeds[i];
ulong modulus = 2UL * seed.Multiplier * seed.Exponent + 1UL;
MontgomeryDivisorData divisor = MontgomeryDivisorDataPool.Shared.FromModulus(modulus);
ulong cycleLength = MersenneDivisorCycles.CalculateCycleLength(modulus, divisor);
ulong cycleLength = MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisor);
ulong reducedExponent = seed.Exponent % cycleLength;
cases[i] = new Pow2MontgomeryModWindowedBenchmarkCase(
seed.Exponent,
@@ -75,6 +77,7 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
reducedExponent);
}
PrimeOrderCalculatorAccelerator.Return(gpu);
return new Pow2MontgomeryModWindowedBenchmarkInputs(cases);
}
@@ -1,6 +1,7 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu.Accelerators;
using PerfectNumbers.Core.Gpu.Kernels;
namespace EvenPerfectBitScanner.Benchmarks;
@@ -31,12 +32,14 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
ulong checksum = 0UL;
Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < cases.Length; i++)
{
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(current.Divisor);
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(gpu, current.Divisor);
}
PrimeOrderCalculatorAccelerator.Return(gpu);
return checksum;
}
@@ -49,12 +52,14 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
ulong checksum = 0UL;
Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < cases.Length; i++)
{
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(current.Divisor);
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(gpu, current.Divisor);
}
PrimeOrderCalculatorAccelerator.Return(gpu);
return checksum;
}
@@ -28,15 +28,17 @@ public class PrimeTesterBenchmarks
PrimeOrderCalculatorAccelerator.WarmUp();
// HeuristicGroupABPrimeTesterAccelerator.WarmUp();
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
_ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]);
_ = HeuristicPrimeTester.IsPrimeGpu(_candidates[0]);
_ = HeuristicPrimeTester.IsPrimeGpu(gpu, _candidates[0]);
_ = Prime.Numbers.IsPrime(_candidates[0]);
PrimeOrderCalculatorAccelerator.Return(gpu);
}
// [Benchmark]
public int HeuristicCpu()
{
HeuristicPrimeTester tester = _heuristicTester;
ulong[] values = _candidates;
int primeCount = 0;
@@ -54,17 +56,18 @@ public class PrimeTesterBenchmarks
[Benchmark]
public int HeuristicGpu()
{
HeuristicPrimeTester tester = _heuristicTester;
ulong[] values = _candidates;
int primeCount = 0;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < values.Length; i++)
{
if (HeuristicPrimeTester.IsPrimeGpu(values[i]))
if (HeuristicPrimeTester.IsPrimeGpu(gpu, values[i]))
{
primeCount++;
}
}
PrimeOrderCalculatorAccelerator.Return(gpu);
return primeCount;
}
@@ -75,9 +78,10 @@ public class PrimeTesterBenchmarks
ulong[] values = _candidates;
int primeCount = 0;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < values.Length; i++)
{
if (HeuristicCombinedPrimeTester.IsPrimeGpu(values[i]))
if (HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, values[i]))
{
primeCount++;
}
@@ -109,14 +113,16 @@ public class PrimeTesterBenchmarks
ulong[] values = _candidates;
int primeCount = 0;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int i = 0; i < values.Length; i++)
{
if (PrimeTester.IsPrimeGpu(values[i]))
if (PrimeTester.IsPrimeGpu(gpu, values[i]))
{
primeCount++;
}
}
PrimeOrderCalculatorAccelerator.Return(gpu);
return primeCount;
}
+40 -17
View File
@@ -6,6 +6,7 @@ using PerfectNumbers.Core.Gpu;
using EvenPerfectBitScanner.Candidates;
using EvenPerfectBitScanner.Candidates.Transforms;
using System.Numerics;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace EvenPerfectBitScanner.Tests;
@@ -104,14 +105,16 @@ public class ProgramTests
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) });
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Program.IsEvenPerfectCandidate(11UL, 32UL, out _, out _).Should().BeFalse();
Program.IsEvenPerfectCandidate(5UL, 32UL, out _, out _).Should().BeTrue();
Program.IsEvenPerfectCandidate(127UL, 32UL, out _, out _).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, 11UL, 32UL, out _, out _).Should().BeFalse();
Program.IsEvenPerfectCandidate(gpu, 5UL, 32UL, out _, out _).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, 127UL, 32UL, out _, out _).Should().BeTrue();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
testerField.SetValue(null, null);
candidatesField.SetValue(null, null);
primeField.SetValue(null, null);
@@ -134,14 +137,16 @@ public class ProgramTests
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, Array.Empty<(ulong, uint)>());
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Program.IsEvenPerfectCandidate(11UL, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
searched.Should().BeTrue();
detailedCheck.Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
testerField.SetValue(null, null);
candidatesField.SetValue(null, null);
primeField.SetValue(null, null);
@@ -163,14 +168,16 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Program.IsEvenPerfectCandidate(3UL, 4_000_000_000UL, out bool searched, out bool detailed).Should().BeFalse();
Program.IsEvenPerfectCandidate(gpu, 3UL, 4_000_000_000UL, out bool searched, out bool detailed).Should().BeFalse();
searched.Should().BeTrue();
detailed.Should().BeTrue();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
testerField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -191,18 +198,20 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Program.IsEvenPerfectCandidate(11UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
searched.Should().BeTrue();
detailed.Should().BeTrue();
Program.IsEvenPerfectCandidate(13UL, 0UL, out searched, out detailed).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, 13UL, 0UL, out searched, out detailed).Should().BeTrue();
searched.Should().BeTrue();
detailed.Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
testerField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -222,18 +231,20 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Program.IsEvenPerfectCandidate(29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
Program.IsEvenPerfectCandidate(gpu, 29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
searched.Should().BeTrue();
detailed.Should().BeFalse();
Program.IsEvenPerfectCandidate(31UL, 0UL, out searched, out detailed).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, 31UL, 0UL, out searched, out detailed).Should().BeTrue();
searched.Should().BeTrue();
detailed.Should().BeTrue();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -256,18 +267,20 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
ulong[] primes = [107UL, 127UL];
foreach (ulong prime in primes)
{
Program.IsEvenPerfectCandidate(prime, 1024UL, out bool searched, out bool detailed).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, prime, 1024UL, out bool searched, out bool detailed).Should().BeTrue();
searched.Should().BeTrue();
detailed.Should().BeTrue();
}
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -291,18 +304,20 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
Parallel.ForEach(primes, prime =>
{
Program.IsEvenPerfectCandidate(prime, 0UL, out bool searched, out bool detailed).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, prime, 0UL, out bool searched, out bool detailed).Should().BeTrue();
searched.Should().BeTrue();
detailed.Should().BeTrue();
});
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -325,18 +340,20 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Program.IsEvenPerfectCandidate(107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, 107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue();
searched.Should().BeTrue();
detailed.Should().BeTrue();
Program.IsEvenPerfectCandidate(127UL, 0UL, out searched, out detailed).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, 127UL, 0UL, out searched, out detailed).Should().BeTrue();
searched.Should().BeTrue();
detailed.Should().BeTrue();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -362,13 +379,15 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
searched.Should().BeTrue();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -397,13 +416,15 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
Program.IsEvenPerfectCandidate(gpu, exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue();
searched.Should().BeTrue();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -476,21 +497,23 @@ public class ProgramTests
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
compositeField.SetValue(null, false);
Program.IsEvenPerfectCandidate(9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
Program.IsEvenPerfectCandidate(gpu, 9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse();
searched.Should().BeFalse();
detailed.Should().BeFalse();
((bool)compositeField.GetValue(null)!).Should().BeTrue();
Program.IsEvenPerfectCandidate(11UL, 0UL, out searched, out detailed);
Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out searched, out detailed);
searched.Should().BeTrue();
((bool)compositeField.GetValue(null)!).Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers();
@@ -2,6 +2,7 @@ using System.Runtime.CompilerServices;
using Open.Numeric.Primes;
using PerfectNumbers.Core;
using EvenPerfectBitScanner.Candidates.Transforms;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace EvenPerfectBitScanner.Candidates;
@@ -53,7 +54,7 @@ internal static class CandidatesCalculator
return trackers.Value!;
}
internal static bool IsCompositeByResidues(ulong p)
internal static bool IsCompositeByResidues(PrimeOrderCalculatorAccelerator gpu, ulong p)
{
ModResidueTracker tracker = AcquireResidueTracker();
// Use ModResidueTracker with a small set of primes to pre-filter composite p.
@@ -71,7 +72,7 @@ internal static class CandidatesCalculator
break;
}
if (tracker.MergeOrAppend(p, primes[primeIndex], out bool divisible) && divisible)
if (tracker.MergeOrAppend(gpu, p, primes[primeIndex], out bool divisible) && divisible)
{
return true;
}
+11 -8
View File
@@ -7,6 +7,7 @@ using Open.Collections;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace EvenPerfectBitScanner;
@@ -469,6 +470,7 @@ internal static class Program
bool passedAllTests, searchedMersenne, detailedCheck;
ArrayPool<ulong> pool = ThreadStaticPools.UlongPool;
ulong[] buffer = pool.Rent(blockSize);
PrimeOrderCalculatorAccelerator gpu = PrimeOrderCalculatorAccelerator.Rent(1);
while (!Volatile.Read(ref _limitReached))
{
count = CandidatesCalculator.ReserveBlock(buffer, blockSize, ref _limitReached);
@@ -482,7 +484,7 @@ internal static class Program
for (j = 0; j < count && !Volatile.Read(ref _limitReached); j++)
{
p = buffer[j];
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
passedAllTests = IsEvenPerfectCandidate(gpu, p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
}
@@ -505,7 +507,7 @@ internal static class Program
continue;
}
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
passedAllTests = IsEvenPerfectCandidate(gpu, p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
if (p == maxP)
@@ -522,6 +524,7 @@ internal static class Program
}
pool.Return(buffer);
PrimeOrderCalculatorAccelerator.Return(gpu);
});
}
@@ -565,7 +568,7 @@ internal static class Program
}
}
internal static bool IsEvenPerfectCandidate(ulong p, ulong divisorCyclesSearchLimit, out bool searchedMersenne, out bool detailedCheck)
internal static bool IsEvenPerfectCandidate(PrimeOrderCalculatorAccelerator gpu, ulong p, ulong divisorCyclesSearchLimit, out bool searchedMersenne, out bool detailedCheck)
{
searchedMersenne = false;
detailedCheck = false;
@@ -615,7 +618,7 @@ internal static class Program
}
// Fast residue-based composite check for p using small primes
if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor && CandidatesCalculator.IsCompositeByResidues(p))
if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor && CandidatesCalculator.IsCompositeByResidues(gpu, p))
{
searchedMersenne = false;
detailedCheck = false;
@@ -628,7 +631,7 @@ internal static class Program
{
if (!(_runPrimesOnCpu
? PrimeTester.IsPrime(p)
: HeuristicCombinedPrimeTester.IsPrimeGpu(p)))
: HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, p)))
{
_lastCompositeP = true;
return false;
@@ -641,7 +644,7 @@ internal static class Program
if (_cliArguments.UseDivisor)
{
bool divisorsExhausted;
bool isPrime = _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out divisorsExhausted);
bool isPrime = _divisorTester!.IsPrime(gpu, p, _divisor, divisorCyclesSearchLimit, out divisorsExhausted);
detailedCheck = divisorsExhausted;
return isPrime;
}
@@ -649,10 +652,10 @@ internal static class Program
if (_cliArguments.UseByDivisor)
{
// Windowed pow2mod kernel handles by-divisor scans.
return _byDivisorTester!.IsPrime(p, out detailedCheck, out _);
return _byDivisorTester!.IsPrime(gpu, p, out detailedCheck, out _);
}
detailedCheck = MersenneTesters.Value!.IsMersennePrime(p);
detailedCheck = MersenneTesters.Value!.IsMersennePrime(gpu, p);
return detailedCheck;
}
}
@@ -1,5 +1,6 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu;
@@ -7,59 +8,91 @@ namespace PerfectNumbers.Core.Tests.Gpu;
[Trait("Category", "Slow")]
public class LucasLehmerPrimeTesterTests
{
[Theory]
[InlineData(3UL, true)]
[InlineData(5UL, true)]
[InlineData(11UL, false)]
[Trait("Category", "Fast")]
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected)
{
new MersenneNumberLucasLehmerGpuTester().IsPrime(p, true).Should().Be(expected);
}
[Theory]
[InlineData(3UL, true)]
[InlineData(5UL, true)]
[InlineData(11UL, false)]
[Trait("Category", "Fast")]
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected)
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
new MersenneNumberLucasLehmerGpuTester().IsPrime(gpu, p, true).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback()
{
// Use a modest exponent that still exercises the NTT path while keeping
// runtime short enough for the unit test environment.
const ulong exponent = 128UL;
var tester = new MersenneNumberLucasLehmerGpuTester();
tester.WarmUpNttParameters(exponent);
tester.IsPrime(exponent, true).Should().BeFalse();
}
[Fact]
[Trait("Category", "Fast")]
public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback()
{
// Use a modest exponent that still exercises the NTT path while keeping
// runtime short enough for the unit test environment.
const ulong exponent = 128UL;
var tester = new MersenneNumberLucasLehmerGpuTester();
tester.WarmUpNttParameters(exponent);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, exponent, true).Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void IsMersennePrimeBatch_matches_individual()
{
ulong[] exponents = new ulong[] { 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 19UL, 23UL };
bool[] expected = new bool[] { true, true, true, false, true, true, true, false };
var tester = new MersenneNumberLucasLehmerGpuTester();
bool[] results = new bool[exponents.Length];
tester.IsMersennePrimeBatch(exponents, results);
results.Should().Equal(expected);
}
[Fact]
[Trait("Category", "Fast")]
public void IsMersennePrimeBatch_matches_individual()
{
ulong[] exponents = new ulong[] { 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 19UL, 23UL };
bool[] expected = new bool[] { true, true, true, false, true, true, true, false };
var tester = new MersenneNumberLucasLehmerGpuTester();
bool[] results = new bool[exponents.Length];
tester.IsMersennePrimeBatch(exponents, results);
results.Should().Equal(expected);
}
[Theory]
[InlineData(132UL)]
[InlineData(1000UL)]
[Trait("Category", "Slow")]
public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent)
{
var tester = new MersenneNumberLucasLehmerGpuTester();
tester.WarmUpNttParameters(exponent);
tester.IsPrime(exponent, true).Should().BeFalse();
}
[Theory]
[InlineData(132UL)]
[InlineData(1000UL)]
[Trait("Category", "Slow")]
public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent)
{
var tester = new MersenneNumberLucasLehmerGpuTester();
tester.WarmUpNttParameters(exponent);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, exponent, true).Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Theory]
[InlineData(3UL, true)]
[InlineData(5UL, true)]
[InlineData(11UL, false)]
[Trait("Category", "Fast")]
public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected)
{
new MersenneNumberLucasLehmerCpuTester().IsPrime(p).Should().Be(expected);
}
[Theory]
[InlineData(3UL, true)]
[InlineData(5UL, true)]
[InlineData(11UL, false)]
[Trait("Category", "Fast")]
public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected)
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
new MersenneNumberLucasLehmerCpuTester().IsPrime(gpu, p).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -2,6 +2,7 @@ using System.Numerics;
using System.Reflection;
using FluentAssertions;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
@@ -9,367 +10,383 @@ namespace PerfectNumbers.Core.Tests;
[Trait("Category", "Fast")]
public class PrimeOrderGpuHeuristicsTests
{
public PrimeOrderGpuHeuristicsTests()
{
PrimeOrderGpuHeuristics.OverflowRegistry.Clear();
PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear();
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
public PrimeOrderGpuHeuristicsTests()
{
PrimeOrderGpuHeuristics.OverflowRegistry.Clear();
PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear();
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
[Fact]
public void TryPow2Mod_returns_overflow_for_marked_prime()
{
const ulong prime = 97UL;
try
{
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
[Fact]
public void TryPow2Mod_returns_overflow_for_marked_prime()
{
const ulong prime = 97UL;
try
{
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
[Fact]
public void TryPow2Mod_returns_success_for_supported_prime()
{
const ulong exponent = 138_000_001UL;
const ulong prime = 138_000_001UL;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData);
[Fact]
public void TryPow2Mod_returns_success_for_supported_prime()
{
const ulong exponent = 138_000_001UL;
const ulong prime = 138_000_001UL;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData);
status.Should().Be(GpuPow2ModStatus.Success);
remainder.Should().Be(2UL);
}
status.Should().Be(GpuPow2ModStatus.Success);
remainder.Should().Be(2UL);
}
[Fact]
public void TryPow2Mod_returns_overflow_when_modulus_exceeds_capability()
{
const ulong prime = 97UL;
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
[Fact]
public void TryPow2Mod_returns_overflow_when_modulus_exceeds_capability()
{
const ulong prime = 97UL;
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
[Fact]
public void TryPow2Mod_returns_overflow_when_exponent_exceeds_capability()
{
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
const ulong prime = 193UL;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, prime, out ulong remainder, divisorData);
[Fact]
public void TryPow2Mod_returns_overflow_when_exponent_exceeds_capability()
{
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
const ulong prime = 193UL;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, prime, out ulong remainder, divisorData);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(0UL);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(0UL);
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, prime, out ulong followUpRemainder, divisorData);
followUp.Should().Be(GpuPow2ModStatus.Success);
followUpRemainder.Should().Be(16UL);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
}
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, prime, out ulong followUpRemainder, divisorData);
followUp.Should().Be(GpuPow2ModStatus.Success);
followUpRemainder.Should().Be(16UL);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
}
[Fact]
public void TryPow2ModBatch_returns_overflow_for_marked_prime()
{
const ulong prime = 193UL;
try
{
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
[Fact]
public void TryPow2ModBatch_returns_overflow_for_marked_prime()
{
const ulong prime = 193UL;
try
{
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
ulong[] remainders = new ulong[2];
ulong[] exponents = { 1UL, 2UL };
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
ulong[] remainders = new ulong[2];
ulong[] exponents = { 1UL, 2UL };
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(0UL);
remainders[1].Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(0UL);
remainders[1].Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
[Fact]
public void TryPow2ModBatch_returns_success_for_supported_prime()
{
ulong[] remainders = { 123UL, 456UL };
ulong[] exponents = { 138_000_001UL, 138_000_011UL };
const ulong prime = 138_000_001UL;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
[Fact]
public void TryPow2ModBatch_returns_success_for_supported_prime()
{
ulong[] remainders = { 123UL, 456UL };
ulong[] exponents = { 138_000_001UL, 138_000_011UL };
const ulong prime = 138_000_001UL;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
ulong expectedFirst = (ulong)BigInteger.ModPow(2, exponents[0], prime);
ulong expectedSecond = (ulong)BigInteger.ModPow(2, exponents[1], prime);
ulong expectedFirst = (ulong)BigInteger.ModPow(2, exponents[0], prime);
ulong expectedSecond = (ulong)BigInteger.ModPow(2, exponents[1], prime);
status.Should().Be(GpuPow2ModStatus.Success);
expectedFirst.Should().Be(2UL);
expectedSecond.Should().Be(2048UL);
remainders[0].Should().Be(expectedFirst);
remainders[1].Should().Be(expectedSecond);
}
status.Should().Be(GpuPow2ModStatus.Success);
expectedFirst.Should().Be(2UL);
expectedSecond.Should().Be(2048UL);
remainders[0].Should().Be(expectedFirst);
remainders[1].Should().Be(expectedSecond);
}
[Fact]
public void TryPow2ModBatch_returns_overflow_when_modulus_exceeds_capability()
{
const ulong prime = 97UL;
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
ulong[] exponents = { 1UL, 2UL };
ulong[] remainders = { 5UL, 6UL };
[Fact]
public void TryPow2ModBatch_returns_overflow_when_modulus_exceeds_capability()
{
const ulong prime = 97UL;
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
ulong[] exponents = { 1UL, 2UL };
ulong[] remainders = { 5UL, 6UL };
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(0UL);
remainders[1].Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(0UL);
remainders[1].Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
[Fact]
public void TryPow2ModBatch_returns_overflow_when_any_exponent_exceeds_capability()
{
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
ulong[] exponents = { 2UL, 16UL };
ulong[] remainders = { 9UL, 11UL };
[Fact]
public void TryPow2ModBatch_returns_overflow_when_any_exponent_exceeds_capability()
{
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
ulong[] exponents = { 2UL, 16UL };
ulong[] remainders = { 9UL, 11UL };
const ulong prime = 193UL;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
const ulong prime = 193UL;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(0UL);
remainders[1].Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(0UL);
remainders[1].Should().Be(0UL);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
}
[Fact]
public void TryPow2ModWide_returns_overflow_for_marked_prime()
{
UInt128 prime = ((UInt128)1 << 80) + 7;
try
{
PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0;
[Fact]
public void TryPow2ModWide_returns_overflow_for_marked_prime()
{
UInt128 prime = ((UInt128)1 << 80) + 7;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0;
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, UInt128.One, prime, out UInt128 remainder);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
public void TryPow2ModWide_returns_success_for_supported_prime()
{
UInt128 prime = ((UInt128)1 << 96) + 11;
UInt128 exponent = ((UInt128)1 << 64) + 3;
[Fact]
public void TryPow2ModWide_returns_success_for_supported_prime()
{
UInt128 prime = ((UInt128)1 << 96) + 11;
UInt128 exponent = ((UInt128)1 << 64) + 3;
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out UInt128 remainder);
PrimeOrderCalculatorAccelerator.Return(gpu);
status.Should().Be(GpuPow2ModStatus.Success);
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime);
remainder.Should().Be(expected);
}
status.Should().Be(GpuPow2ModStatus.Success);
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime);
remainder.Should().Be(expected);
}
[Theory]
[InlineData("193", "31")]
[InlineData("193", "255")]
[InlineData("79228162514264337593543950347", "36893488147419103251")]
public void Pow2ModKernelCore_matches_cpu_windowed_path(string modulusText, string exponentText)
{
UInt128 modulus = UInt128.Parse(modulusText);
UInt128 exponent = UInt128.Parse(exponentText);
[Theory]
[InlineData("193", "31")]
[InlineData("193", "255")]
[InlineData("79228162514264337593543950347", "36893488147419103251")]
public void Pow2ModKernelCore_matches_cpu_windowed_path(string modulusText, string exponentText)
{
UInt128 modulus = UInt128.Parse(modulusText);
UInt128 exponent = UInt128.Parse(exponentText);
var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull();
var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull();
object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) });
GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!;
UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus);
object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) });
GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!;
UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus);
((UInt128)gpuResult).Should().Be(expected);
}
((UInt128)gpuResult).Should().Be(expected);
}
[Fact]
public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 80) + 9;
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder);
[Fact]
public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 80) + 9;
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, UInt128.One, prime, out UInt128 remainder);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability()
{
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
UInt128 exponent = UInt128.One << 8;
UInt128 prime = ((UInt128)1 << 70) + 3;
[Fact]
public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability()
{
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
UInt128 exponent = UInt128.One << 8;
UInt128 prime = ((UInt128)1 << 70) + 3;
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out UInt128 remainder);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainder.Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
public void TryPow2ModBatchWide_returns_success_for_supported_prime()
{
UInt128 prime = ((UInt128)1 << 90) + 19;
UInt128[] exponents = { UInt128.One, ((UInt128)1 << 64) + 5 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
[Fact]
public void TryPow2ModBatchWide_returns_success_for_supported_prime()
{
UInt128 prime = ((UInt128)1 << 90) + 19;
UInt128[] exponents = { UInt128.One, ((UInt128)1 << 64) + 5 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
PrimeOrderCalculatorAccelerator.Return(gpu);
status.Should().Be(GpuPow2ModStatus.Success);
for (int i = 0; i < exponents.Length; i++)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime);
remainders[i].Should().Be(expected);
}
}
status.Should().Be(GpuPow2ModStatus.Success);
for (int i = 0; i < exponents.Length; i++)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime);
remainders[i].Should().Be(expected);
}
}
[Fact]
public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 88) + 13;
UInt128[] exponents = { UInt128.One, (UInt128)5 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
[Fact]
public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 88) + 13;
UInt128[] exponents = { UInt128.One, (UInt128)5 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(UInt128.Zero);
remainders[1].Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(UInt128.Zero);
remainders[1].Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _);
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 72) + 23;
UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
[Fact]
public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability()
{
UInt128 prime = ((UInt128)1 << 72) + 23;
UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 };
UInt128[] remainders = { UInt128.Zero, UInt128.Zero };
var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6);
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders);
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(UInt128.Zero);
remainders[1].Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
}
}
status.Should().Be(GpuPow2ModStatus.Overflow);
remainders[0].Should().Be(UInt128.Zero);
remainders[1].Should().Be(UInt128.Zero);
}
finally
{
PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
// This will never happen in production code because the caller ensures the remainder span is large enough.
// [Fact]
// public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
// {
// ulong[] remainders = new ulong[1];
// ulong[] exponents = { 1UL, 2UL };
// This will never happen in production code because the caller ensures the remainder span is large enough.
// [Fact]
// public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
// {
// ulong[] remainders = new ulong[1];
// ulong[] exponents = { 1UL, 2UL };
// Action act = () =>
// {
// const ulong prime = 5UL;
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
// PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
// };
// Action act = () =>
// {
// const ulong prime = 5UL;
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
// PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
// };
// act.Should().Throw<ArgumentException>();
// }
// act.Should().Throw<ArgumentException>();
// }
[Fact]
public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked()
{
const ulong prime = 7UL;
try
{
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
[Fact]
public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked()
{
const ulong prime = 7UL;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime);
ulong result = PrimeOrderCalculator.Calculate(gpu, prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
result.Should().Be(3UL);
}
finally
{
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
result.Should().Be(3UL);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _);
}
}
}
@@ -1,4 +1,5 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu;
@@ -6,26 +7,39 @@ namespace PerfectNumbers.Core.Tests.Gpu;
[Trait("Category", "Fast")]
public class PrimeTesterGpuTests
{
[Fact]
public void IsPrimeGpu_accepts_known_primes()
{
var tester = new PrimeTester();
[Fact]
public void IsPrimeGpu_accepts_known_primes()
{
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
foreach (ulong prime in primes)
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
foreach (ulong prime in primes)
{
PrimeTester.IsPrimeGpu(gpu, prime).Should().BeTrue();
}
}
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
public void IsPrimeGpu_rejects_composites()
{
var tester = new PrimeTester();
[Fact]
public void IsPrimeGpu_rejects_composites()
{
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
foreach (ulong composite in composites)
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
PrimeTester.IsPrimeGpu(composite).Should().BeFalse();
foreach (ulong composite in composites)
{
PrimeTester.IsPrimeGpu(gpu, composite).Should().BeFalse();
}
}
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,107 +1,112 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class MersenneDivisorCyclesGenerateGpuTests
{
[Fact]
[Trait("Category", "Fast")]
public void GenerateGpu_produces_expected_cycles_for_small_range()
{
string path = Path.GetTempFileName();
try
{
MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
var pairs = new List<(ulong divisor, ulong cycle)>();
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
using var reader = new BinaryReader(stream);
while (stream.Position < stream.Length)
{
ulong d = reader.ReadUInt64();
ulong c = reader.ReadUInt64();
pairs.Add((d, c));
}
[Fact]
[Trait("Category", "Fast")]
public void GenerateGpu_produces_expected_cycles_for_small_range()
{
string path = Path.GetTempFileName();
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
var pairs = new List<(ulong divisor, ulong cycle)>();
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
using var reader = new BinaryReader(stream);
while (stream.Position < stream.Length)
{
ulong d = reader.ReadUInt64();
ulong c = reader.ReadUInt64();
pairs.Add((d, c));
}
var expected = new List<ulong>();
for (ulong d = 3; d <= 50; d++)
{
if ((d & 1UL) == 0UL || d % 3UL == 0UL || d % 5UL == 0UL || d % 7UL == 0UL || d % 11UL == 0UL)
{
continue;
}
var expected = new List<ulong>();
for (ulong d = 3; d <= 50; d++)
{
if ((d & 1UL) == 0UL || d % 3UL == 0UL || d % 5UL == 0UL || d % 7UL == 0UL || d % 11UL == 0UL)
{
continue;
}
expected.Add(d);
}
expected.Add(d);
}
pairs.Should().HaveCount(expected.Count);
foreach (var pair in pairs)
{
expected.Should().Contain(pair.divisor);
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor);
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
}
}
finally
{
if (File.Exists(path))
{
File.Delete(path);
}
}
}
pairs.Should().HaveCount(expected.Count);
foreach (var pair in pairs)
{
expected.Should().Contain(pair.divisor);
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor);
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(gpu, pair.divisor, divisorData));
}
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
if (File.Exists(path))
{
File.Delete(path);
}
}
}
[Fact]
[Trait("Category", "Fast")]
public void Generate_produces_unique_cycles_for_expected_divisors()
{
string path = Path.GetTempFileName();
try
{
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
[Fact]
[Trait("Category", "Fast")]
public void Generate_produces_unique_cycles_for_expected_divisors()
{
string path = Path.GetTempFileName();
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
var pairs = new List<(ulong divisor, ulong cycle)>();
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
using var reader = new BinaryReader(stream);
while (stream.Position < stream.Length)
{
ulong divisor = reader.ReadUInt64();
ulong cycle = reader.ReadUInt64();
pairs.Add((divisor, cycle));
}
var pairs = new List<(ulong divisor, ulong cycle)>();
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
using var reader = new BinaryReader(stream);
while (stream.Position < stream.Length)
{
ulong divisor = reader.ReadUInt64();
ulong cycle = reader.ReadUInt64();
pairs.Add((divisor, cycle));
}
var expected = new List<ulong>();
for (ulong divisor = 3; divisor <= 50; divisor++)
{
if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL)
{
continue;
}
var expected = new List<ulong>();
for (ulong divisor = 3; divisor <= 50; divisor++)
{
if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL)
{
continue;
}
expected.Add(divisor);
}
expected.Add(divisor);
}
pairs.Should().HaveCount(expected.Count);
pairs.Should().HaveCount(expected.Count);
HashSet<ulong> uniqueDivisors = new();
foreach (var pair in pairs)
{
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor);
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
}
HashSet<ulong> uniqueDivisors = new();
foreach (var pair in pairs)
{
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor);
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(gpu, pair.divisor, divisorData));
}
uniqueDivisors.Should().BeEquivalentTo(expected);
uniqueDivisors.Should().BeEquivalentTo(expected);
var (_, completeCount) = MersenneDivisorCycles.FindLast(path);
completeCount.Should().Be(expected.Count);
}
finally
{
if (File.Exists(path))
{
File.Delete(path);
}
}
}
var (_, completeCount) = MersenneDivisorCycles.FindLast(path);
completeCount.Should().Be(expected.Count);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
if (File.Exists(path))
{
File.Delete(path);
}
}
}
}
@@ -1,26 +1,35 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class MersenneDivisorCyclesTests
{
public static IEnumerable<object[]> Divisors() =>
new[]
{
new object[] {3UL, 2UL},
new object[] {7UL, 3UL},
new object[] {11UL, 10UL},
};
public static IEnumerable<object[]> Divisors() =>
new[]
{
new object[] {3UL, 2UL},
new object[] {7UL, 3UL},
new object[] {11UL, 10UL},
};
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(Divisors))]
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(divisor);
MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData).Should().Be(expected);
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(Divisors))]
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(divisor);
MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData).Should().Be(expected);
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,4 +1,5 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
@@ -57,7 +58,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
return DivisorLimit;
}
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor)
{
divisorsExhausted = true;
if (prime % 7UL == 0UL)
@@ -76,7 +77,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
return true;
}
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
{
return new DummySession();
}
@@ -1,107 +1,122 @@
using FluentAssertions;
using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class MersenneNumberDivisorCpuTesterTests
{
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_tracks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(11UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_tracks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(11UL);
tester.IsPrime(5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, 5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(7UL, out divisorsExhausted, out divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(gpu, 7UL, out divisorsExhausted, out divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(11UL, out divisorsExhausted, out divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
}
tester.IsPrime(gpu, 11UL, out divisorsExhausted, out divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_checks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(13UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_checks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(13UL);
var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
byte[] hits = new byte[primes.Length];
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
byte[] hits = new byte[primes.Length];
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL));
session.CheckDivisor(23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL), cycle23, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(gpu, 23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL));
session.CheckDivisor(23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL), cycle23, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
Array.Fill(hits, (byte)0);
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL));
session.CheckDivisor(31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL), cycle31, primes, hits);
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
}
finally
{
session.Return();
}
}
Array.Fill(hits, (byte)0);
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(gpu, 31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL));
session.CheckDivisor(31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL), cycle31, primes, hits);
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(43UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(43UL);
var session = tester.CreateDivisorSession();
try
{
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
byte[] hits = new byte[exponents.Length];
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
byte[] hits = new byte[exponents.Length];
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 7UL, divisorData);
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
finally
{
session.Return();
}
}
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(61UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(61UL);
var session = tester.CreateDivisorSession();
try
{
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
byte[] hits = new byte[exponents.Length];
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
byte[] hits = new byte[exponents.Length];
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 11UL, divisorData);
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
finally
{
session.Return();
}
}
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -2,366 +2,440 @@ using System.Reflection;
using FluentAssertions;
using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class MersenneNumberDivisorGpuTesterTests
{
[Theory]
[InlineData(33UL, 7UL, true)]
[InlineData(35UL, 31UL, true)]
[InlineData(37UL, 223UL, true)]
[InlineData(33UL, 13UL, false)]
[InlineData(35UL, 73UL, false)]
[InlineData(37UL, 227UL, false)]
[Trait("Category", "Fast")]
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
{
var tester = new MersenneNumberDivisorGpuTester();
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
tester.IsDivisible(exponent, in divisorValue).Should().Be(expected);
}
[Theory]
[InlineData(33UL, 7UL, true)]
[InlineData(35UL, 31UL, true)]
[InlineData(37UL, 223UL, true)]
[InlineData(33UL, 13UL, false)]
[InlineData(35UL, 73UL, false)]
[InlineData(37UL, 227UL, false)]
[Trait("Category", "Fast")]
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
{
var tester = new MersenneNumberDivisorGpuTester();
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
tester.IsDivisible(exponent, in divisorValue).Should().Be(expected);
}
[Fact]
[Trait("Category", "Fast")]
public void IsDivisible_handles_large_divisors()
{
var tester = new MersenneNumberDivisorGpuTester();
UInt128 divisor = (UInt128.One << 65) - UInt128.One;
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
tester.IsDivisible(65UL, in divisorValue).Should().BeTrue();
divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate.
tester.IsDivisible(65UL, in divisorValue).Should().BeFalse();
}
[Fact]
[Trait("Category", "Fast")]
public void IsDivisible_handles_large_divisors()
{
var tester = new MersenneNumberDivisorGpuTester();
UInt128 divisor = (UInt128.One << 65) - UInt128.One;
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
tester.IsDivisible(65UL, in divisorValue).Should().BeTrue();
divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate.
tester.IsDivisible(65UL, in divisorValue).Should().BeFalse();
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrime_sets_divisorsExhausted_false_when_search_range_not_exhausted()
{
var tester = new MersenneNumberDivisorGpuTester();
typeof(MersenneNumberDivisorGpuTester)
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
.SetValue(null, Array.Empty<(ulong, uint)>());
[Fact]
[Trait("Category", "Fast")]
public void IsPrime_sets_divisorsExhausted_false_when_search_range_not_exhausted()
{
var tester = new MersenneNumberDivisorGpuTester();
typeof(MersenneNumberDivisorGpuTester)
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
.SetValue(null, Array.Empty<(ulong, uint)>());
tester.IsPrime(31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeFalse();
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, 31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
{
var tester = new MersenneNumberDivisorGpuTester();
tester.IsPrime(35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
{
var tester = new MersenneNumberDivisorGpuTester();
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, 35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
[Fact]
[Trait("Category", "Fast")]
public void IsPrime_accepts_large_search_limits()
{
var tester = new MersenneNumberDivisorGpuTester();
tester.IsPrime(35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
exhausted.Should().BeTrue();
}
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_tracks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL);
[Fact]
[Trait("Category", "Fast")]
public void IsPrime_accepts_large_search_limits()
{
var tester = new MersenneNumberDivisorGpuTester();
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, 35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
exhausted.Should().BeTrue();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
tester.IsPrime(31UL, out bool exhausted, out ulong divisor).Should().BeTrue();
exhausted.Should().BeTrue();
divisor.Should().Be(0UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_tracks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL);
tester.IsPrime(37UL, out exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, 31UL, out bool exhausted, out ulong divisor).Should().BeTrue();
exhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(43UL, out exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
}
tester.IsPrime(gpu, 37UL, out exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_defaults_to_zero_limit_before_configuration()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.IsPrime(gpu, 43UL, out exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
tester.DivisorLimit.Should().Be(0UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_defaults_to_zero_limit_before_configuration()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
ulong[] primes = { 31UL, 37UL };
ulong[] allowed = new ulong[primes.Length];
tester.DivisorLimit.Should().Be(0UL);
tester.PrepareCandidates(primes, allowed);
allowed.Should().OnlyContain(value => value == 0UL);
}
ulong[] primes = { 31UL, 37UL };
ulong[] allowed = new ulong[primes.Length];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_respects_filter_based_limit()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(41UL);
tester.PrepareCandidates(primes, allowed);
allowed.Should().OnlyContain(value => value == 0UL);
}
tester.IsPrime(31UL, out _, out ulong divisor).Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_respects_filter_based_limit()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(41UL);
tester.IsPrime(41UL, out bool divisorsExhausted, out divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, 31UL, out _, out ulong divisor).Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(gpu, 37UL, out bool exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
tester.IsPrime(gpu, 41UL, out bool divisorsExhausted, out divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Theory]
[InlineData(71UL, false)]
[InlineData(89UL, true)]
[InlineData(127UL, true)]
[Trait("Category", "Fast")]
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(exponent);
[Theory]
[InlineData(71UL, false)]
[InlineData(89UL, true)]
[InlineData(127UL, true)]
[Trait("Category", "Fast")]
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(exponent);
tester.IsPrime(exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime);
exhausted.Should().BeTrue();
if (expectedPrime)
{
divisor.Should().Be(0UL);
}
else
{
divisor.Should().BeGreaterThan(0UL);
}
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester
{
GpuBatchSize = 8,
};
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime);
tester.ConfigureFromMaxPrime(43UL);
exhausted.Should().BeTrue();
if (expectedPrime)
{
divisor.Should().Be(0UL);
}
else
{
divisor.Should().BeGreaterThan(0UL);
}
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
tester.IsPrime(41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
}
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester
{
GpuBatchSize = 8,
};
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_computes_cycle_when_zero_is_provided()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL);
tester.ConfigureFromMaxPrime(43UL);
var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 31UL, 37UL, 41UL };
byte[] hits = new byte[primes.Length];
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsPrime(gpu, 41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), 0UL, primes, hits);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_computes_cycle_when_zero_is_provided()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL);
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
}
finally
{
session.Return();
}
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] primes = { 31UL, 37UL, 41UL };
byte[] hits = new byte[primes.Length];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_checks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL);
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), 0UL, primes, hits);
var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
byte[] hits = new byte[primes.Length];
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_checks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL);
Array.Fill(hits, (byte)0);
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL));
session.CheckDivisor(13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL), cycle13367, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
}
finally
{
session.Return();
}
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
byte[] hits = new byte[primes.Length];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_gpu_session_reuses_cycle_remainders_across_batches()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester
{
GpuBatchSize = 2,
};
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
tester.ConfigureFromMaxPrime(47UL);
Array.Fill(hits, (byte)0);
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(gpu, 13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL));
session.CheckDivisor(13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL), cycle13367, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
byte[] hits = new byte[primes.Length];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_gpu_session_reuses_cycle_remainders_across_batches()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester
{
GpuBatchSize = 2,
};
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
tester.ConfigureFromMaxPrime(47UL);
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
}
finally
{
session.Return();
}
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
byte[] hits = new byte[primes.Length];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester
{
GpuBatchSize = 5,
};
tester.ConfigureFromMaxPrime(43UL);
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
var session = tester.CreateDivisorSession();
try
{
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
byte[] hits = new byte[exponents.Length];
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester
{
GpuBatchSize = 5,
};
tester.ConfigureFromMaxPrime(43UL);
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
byte[] hits = new byte[exponents.Length];
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
finally
{
session.Return();
}
}
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 7UL, divisorData);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester
{
GpuBatchSize = 5,
};
tester.ConfigureFromMaxPrime(61UL);
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
var session = tester.CreateDivisorSession();
try
{
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
byte[] hits = new byte[exponents.Length];
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester
{
GpuBatchSize = 5,
};
tester.ConfigureFromMaxPrime(61UL);
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
byte[] hits = new byte[exponents.Length];
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
finally
{
session.Return();
}
}
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 11UL, divisorData);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
{
var cycles = MersenneDivisorCycles.Shared;
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
try
{
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
{
var cycles = MersenneDivisorCycles.Shared;
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(191UL);
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(191UL, divisorData);
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
tableField.SetValue(cycles, patchedTable);
smallCyclesField.SetValue(cycles, patchedSmall);
DivisorCycleCache.Shared.RefreshSnapshot();
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(19UL);
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(191UL);
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(gpu, 191UL, divisorData);
typeof(MersenneNumberDivisorByDivisorGpuTester)
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(tester, 200UL);
tableField.SetValue(cycles, patchedTable);
smallCyclesField.SetValue(cycles, patchedSmall);
DivisorCycleCache.Shared.RefreshSnapshot();
tester.IsPrime(19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
}
finally
{
tableField.SetValue(cycles, originalTable);
smallCyclesField.SetValue(cycles, originalSmall);
DivisorCycleCache.Shared.RefreshSnapshot();
}
}
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(19UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_cpu_session_reuses_cycle_remainders_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester
{
BatchSize = 2,
};
typeof(MersenneNumberDivisorByDivisorGpuTester)
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(tester, 200UL);
tester.ConfigureFromMaxPrime(47UL);
tester.IsPrime(gpu, 19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
tableField.SetValue(cycles, originalTable);
smallCyclesField.SetValue(cycles, originalSmall);
DivisorCycleCache.Shared.RefreshSnapshot();
}
}
var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
byte[] hits = new byte[primes.Length];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_cpu_session_reuses_cycle_remainders_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester
{
BatchSize = 2,
};
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
tester.ConfigureFromMaxPrime(47UL);
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
}
finally
{
session.Return();
}
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var session = tester.CreateDivisorSession(gpu);
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
byte[] hits = new byte[primes.Length];
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL));
session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
}
finally
{
session.Return();
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,5 +1,6 @@
using FluentAssertions;
using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
using UInt128 = System.UInt128;
@@ -7,30 +8,38 @@ namespace PerfectNumbers.Core.Tests;
public class MersenneNumberIncrementalCpuTesterTests
{
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
[Theory]
[InlineData(GpuKernelType.Incremental)]
[InlineData(GpuKernelType.Pow2Mod)]
[Trait("Category", "Fast")]
public void Scan_handles_various_prime_exponents(GpuKernelType type)
{
var tester = new MersenneNumberIncrementalCpuTester(type);
[Theory]
[InlineData(GpuKernelType.Incremental)]
[InlineData(GpuKernelType.Pow2Mod)]
[Trait("Category", "Fast")]
public void Scan_handles_various_prime_exponents(GpuKernelType type)
{
var tester = new MersenneNumberIncrementalCpuTester(type);
if (type == GpuKernelType.Pow2Mod)
{
RunCase(tester, 11UL, 1UL, expectedPrime: false);
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
}
if (type == GpuKernelType.Pow2Mod)
{
RunCase(tester, 11UL, 1UL, expectedPrime: false);
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
}
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
}
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
}
private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
{
bool isPrime = true;
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
isPrime.Should().Be(expectedPrime);
}
private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
{
bool isPrime = true;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
isPrime.Should().Be(expectedPrime);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,5 +1,6 @@
using FluentAssertions;
using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
using UInt128 = System.UInt128;
@@ -7,31 +8,39 @@ namespace PerfectNumbers.Core.Tests;
public class MersenneNumberOrderCpuTesterTests
{
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
[Theory]
[InlineData(GpuKernelType.Incremental)]
[InlineData(GpuKernelType.Pow2Mod)]
[Trait("Category", "Fast")]
public void Scan_handles_various_prime_exponents(GpuKernelType type)
{
var tester = new MersenneNumberOrderCpuTester(type);
[Theory]
[InlineData(GpuKernelType.Incremental)]
[InlineData(GpuKernelType.Pow2Mod)]
[Trait("Category", "Fast")]
public void Scan_handles_various_prime_exponents(GpuKernelType type)
{
var tester = new MersenneNumberOrderCpuTester(type);
if (type == GpuKernelType.Pow2Mod)
{
RunCase(tester, 23UL, 1UL, expectedPrime: false);
RunCase(tester, 29UL, 36UL, expectedPrime: false);
}
if (type == GpuKernelType.Pow2Mod)
{
RunCase(tester, 23UL, 1UL, expectedPrime: false);
RunCase(tester, 29UL, 36UL, expectedPrime: false);
}
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
}
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
}
private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
{
bool isPrime = true;
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
isPrime.Should().Be(expectedPrime);
}
private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
{
bool isPrime = true;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
isPrime.Should().Be(expectedPrime);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,5 +1,6 @@
using FluentAssertions;
using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
using UInt128 = System.UInt128;
@@ -7,24 +8,32 @@ namespace PerfectNumbers.Core.Tests;
public class MersenneNumberResidueCpuTesterTests
{
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
[Fact]
[Trait("Category", "Fast")]
public void Scan_handles_various_prime_exponents()
{
var tester = new MersenneNumberResidueCpuTester();
[Fact]
[Trait("Category", "Fast")]
public void Scan_handles_various_prime_exponents()
{
var tester = new MersenneNumberResidueCpuTester();
RunCase(tester, 23UL, 1UL, expectedPrime: false);
RunCase(tester, 29UL, 36UL, expectedPrime: false);
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
}
RunCase(tester, 23UL, 1UL, expectedPrime: false);
RunCase(tester, 29UL, 36UL, expectedPrime: false);
RunCase(tester, 89UL, 1_000UL, expectedPrime: true);
RunCase(tester, 127UL, 1_000UL, expectedPrime: true);
}
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
{
bool isPrime = true;
tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
isPrime.Should().Be(expectedPrime);
}
private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime)
{
bool isPrime = true;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime);
isPrime.Should().Be(expectedPrime);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,4 +1,5 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
@@ -7,30 +8,54 @@ namespace PerfectNumbers.Core.Tests;
[Trait("Category", "Fast")]
public class MersenneNumberTesterTests
{
[Theory]
[InlineData(136_000_002UL)]
[InlineData(136_000_005UL)]
public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p)
{
var tester = new MersenneNumberTester();
tester.IsMersennePrime(p).Should().BeFalse();
}
[Theory]
[InlineData(136_000_002UL)]
[InlineData(136_000_005UL)]
public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p)
{
var tester = new MersenneNumberTester();
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsMersennePrime(gpu, p).Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Theory]
[InlineData(125UL)]
public void WarmUpOrders_populates_cache_without_affecting_results(ulong p)
{
var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true);
tester.WarmUpOrders(p, 1_000UL);
tester.IsMersennePrime(p).Should().BeFalse();
}
[Theory]
[InlineData(125UL)]
public void WarmUpOrders_populates_cache_without_affecting_results(ulong p)
{
var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true);
tester.WarmUpOrders(p, 1_000UL);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsMersennePrime(gpu, p).Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Theory]
[InlineData(125UL, true)]
[InlineData(127UL, false)]
public void SharesFactorWithExponentMinusOne_detects_common_factors(ulong p, bool expected)
{
p.SharesFactorWithExponentMinusOne().Should().Be(expected);
}
[Theory]
[InlineData(125UL, true)]
[InlineData(127UL, false)]
public void SharesFactorWithExponentMinusOne_detects_common_factors(ulong p, bool expected)
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
p.SharesFactorWithExponentMinusOne(gpu).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,4 +1,5 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
@@ -6,27 +7,43 @@ namespace PerfectNumbers.Core.Tests;
[Trait("Category", "Fast")]
public class MersenneResidueModeTests
{
[Fact]
public void IsMersennePrime_residue_mode_rejects_composite_exponent()
{
var tester = new MersenneNumberTester(
useResidue: true,
useIncremental: false,
useGpuScan: false,
useGpuOrder: false);
tester.IsMersennePrime(136_000_002UL).Should().BeFalse();
}
[Fact]
public void IsMersennePrime_residue_mode_rejects_composite_exponent()
{
var tester = new MersenneNumberTester(
useResidue: true,
useIncremental: false,
useGpuScan: false,
useGpuOrder: false);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsMersennePrime(gpu, 136_000_002UL).Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
public void IsMersennePrime_residue_mode_accepts_prime_exponent()
{
var tester = new MersenneNumberTester(
useResidue: true,
useIncremental: true,
useGpuScan: false,
useGpuOrder: false,
maxK: 1_000UL);
tester.IsMersennePrime(31UL).Should().BeTrue();
}
[Fact]
public void IsMersennePrime_residue_mode_accepts_prime_exponent()
{
var tester = new MersenneNumberTester(
useResidue: true,
useIncremental: true,
useGpuScan: false,
useGpuOrder: false,
maxK: 1_000UL);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tester.IsMersennePrime(gpu, 31UL).Should().BeTrue();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,4 +1,5 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
@@ -6,61 +7,93 @@ namespace PerfectNumbers.Core.Tests;
[Trait("Category", "Fast")]
public class ModResidueTrackerTests
{
[Fact]
public void IsDivisible_advances_residue_for_identity()
{
var tracker = new ModResidueTracker(ResidueModel.Identity);
[Fact]
public void IsDivisible_advances_residue_for_identity()
{
var tracker = new ModResidueTracker(ResidueModel.Identity);
tracker.IsDivisible(5, 2).Should().BeFalse();
tracker.IsDivisible(6, 2).Should().BeTrue();
tracker.IsDivisible(7, 2).Should().BeFalse();
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tracker.IsDivisible(gpu, 5, 2).Should().BeFalse();
tracker.IsDivisible(gpu, 6, 2).Should().BeTrue();
tracker.IsDivisible(gpu, 7, 2).Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Theory]
[InlineData(3UL, 7UL, true)]
[InlineData(5UL, 31UL, true)]
[InlineData(5UL, 7UL, false)]
[InlineData(11UL, 23UL, true)]
[InlineData(11UL, 31UL, false)]
public void IsDivisible_handles_mersenne_model(ulong p, ulong divisor, bool expected)
{
var tracker = new ModResidueTracker(ResidueModel.Mersenne);
[Theory]
[InlineData(3UL, 7UL, true)]
[InlineData(5UL, 31UL, true)]
[InlineData(5UL, 7UL, false)]
[InlineData(11UL, 23UL, true)]
[InlineData(11UL, 31UL, false)]
public void IsDivisible_handles_mersenne_model(ulong p, ulong divisor, bool expected)
{
var tracker = new ModResidueTracker(ResidueModel.Mersenne);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
tracker.IsDivisible(p, divisor).Should().Be(expected);
}
try
{
tracker.IsDivisible(gpu, p, divisor).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
public void MergeOrAppend_tracks_existing_and_new_divisors()
{
var tracker = new ModResidueTracker(ResidueModel.Identity);
[Fact]
public void MergeOrAppend_tracks_existing_and_new_divisors()
{
var tracker = new ModResidueTracker(ResidueModel.Identity);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
tracker.BeginMerge(10);
tracker.MergeOrAppend(10, 3, out bool d3).Should().BeTrue();
d3.Should().BeFalse();
tracker.MergeOrAppend(10, 5, out bool d5).Should().BeTrue();
d5.Should().BeTrue();
try
{
tracker.BeginMerge(10);
tracker.MergeOrAppend(gpu, 10, 3, out bool d3).Should().BeTrue();
d3.Should().BeFalse();
tracker.MergeOrAppend(gpu, 10, 5, out bool d5).Should().BeTrue();
d5.Should().BeTrue();
tracker.BeginMerge(10);
tracker.MergeOrAppend(10, 4, out bool d4).Should().BeTrue();
d4.Should().BeFalse();
tracker.BeginMerge(10);
tracker.MergeOrAppend(gpu, 10, 4, out bool d4).Should().BeTrue();
d4.Should().BeFalse();
tracker.BeginMerge(11);
tracker.MergeOrAppend(11, 3, out d3).Should().BeTrue();
d3.Should().BeFalse();
tracker.MergeOrAppend(11, 4, out d4).Should().BeTrue();
d4.Should().BeFalse();
tracker.MergeOrAppend(11, 5, out d5).Should().BeTrue();
d5.Should().BeFalse();
}
tracker.BeginMerge(11);
tracker.MergeOrAppend(gpu, 11, 3, out d3).Should().BeTrue();
d3.Should().BeFalse();
tracker.MergeOrAppend(gpu, 11, 4, out d4).Should().BeTrue();
d4.Should().BeFalse();
tracker.MergeOrAppend(gpu, 11, 5, out d5).Should().BeTrue();
d5.Should().BeFalse();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
public void IsDivisible_requires_non_decreasing_numbers()
{
var tracker = new ModResidueTracker(ResidueModel.Identity);
tracker.IsDivisible(5, 2);
[Fact]
public void IsDivisible_requires_non_decreasing_numbers()
{
var tracker = new ModResidueTracker(ResidueModel.Identity);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
tracker.IsDivisible(gpu, 5, 2);
Action act = () => tracker.IsDivisible(4, 2);
act.Should().Throw<InvalidOperationException>();
}
Action act = () => tracker.IsDivisible(gpu, 4, 2);
act.Should().Throw<InvalidOperationException>();
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,198 +1,236 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class PrimeOrderCalculatorTests
{
public static IEnumerable<object[]> SmallPrimes()
{
yield return new object[] { 3UL };
yield return new object[] { 5UL };
yield return new object[] { 7UL };
yield return new object[] { 11UL };
yield return new object[] { 13UL };
yield return new object[] { 17UL };
yield return new object[] { 19UL };
yield return new object[] { 31UL };
yield return new object[] { 61UL };
yield return new object[] { 89UL };
yield return new object[] { 127UL };
yield return new object[] { 521UL };
}
public static IEnumerable<object[]> SmallPrimes()
{
yield return new object[] { 3UL };
yield return new object[] { 5UL };
yield return new object[] { 7UL };
yield return new object[] { 11UL };
yield return new object[] { 13UL };
yield return new object[] { 17UL };
yield return new object[] { 19UL };
yield return new object[] { 31UL };
yield return new object[] { 61UL };
yield return new object[] { 89UL };
yield return new object[] { 127UL };
yield return new object[] { 521UL };
}
public static IEnumerable<object[]> PrimesWithPrevious()
{
yield return new object[] { 5UL, 3UL };
yield return new object[] { 7UL, 5UL };
yield return new object[] { 11UL, 7UL };
yield return new object[] { 13UL, 11UL };
yield return new object[] { 17UL, 13UL };
yield return new object[] { 19UL, 17UL };
yield return new object[] { 31UL, 29UL };
yield return new object[] { 61UL, 59UL };
yield return new object[] { 89UL, 83UL };
yield return new object[] { 127UL, 113UL };
yield return new object[] { 521UL, 509UL };
}
public static IEnumerable<object[]> PrimesWithPrevious()
{
yield return new object[] { 5UL, 3UL };
yield return new object[] { 7UL, 5UL };
yield return new object[] { 11UL, 7UL };
yield return new object[] { 13UL, 11UL };
yield return new object[] { 17UL, 13UL };
yield return new object[] { 19UL, 17UL };
yield return new object[] { 31UL, 29UL };
yield return new object[] { 61UL, 59UL };
yield return new object[] { 89UL, 83UL };
yield return new object[] { 127UL, 113UL };
yield return new object[] { 521UL, 509UL };
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(SmallPrimes))]
public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime)
{
ulong expected = ComputeOrderByDoubling(prime);
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(SmallPrimes))]
public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime)
{
ulong expected = ComputeOrderByDoubling(prime);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
ulong result = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
ulong result = PrimeOrderCalculator.Calculate(
gpu,
prime,
previousOrder: null,
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
result.Should().Be(expected);
}
PrimeOrderCalculatorAccelerator.Return(gpu);
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(PrimesWithPrevious))]
public void Calculate_heuristic_mode_matches_strict_mode_even_with_previous_order(ulong prime, ulong previousPrime)
{
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
result.Should().Be(expected);
}
ulong heuristic = PrimeOrderCalculator.Calculate(
prime,
previousOrder,
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(PrimesWithPrevious))]
public void Calculate_heuristic_mode_matches_strict_mode_even_with_previous_order(ulong prime, ulong previousPrime)
{
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
ulong strict = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
ulong heuristic = PrimeOrderCalculator.Calculate(
gpu,
prime,
previousOrder,
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
heuristic.Should().Be(strict);
}
ulong strict = PrimeOrderCalculator.Calculate(
gpu,
prime,
previousOrder: null,
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 1UL)]
[InlineData(3UL, 2UL)]
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
{
ulong result = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculatorAccelerator.Return(gpu);
result.Should().Be(expectedOrder);
}
heuristic.Should().Be(strict);
}
[Fact]
[Trait("Category", "Fast")]
public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored()
{
var config = new PrimeOrderCalculator.PrimeOrderSearchConfig(
smallFactorLimit: 1,
pollardRhoMilliseconds: 0,
maxPowChecks: 8,
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 1UL)]
[InlineData(3UL, 2UL)]
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
ulong result = PrimeOrderCalculator.Calculate(
gpu,
prime,
previousOrder: null,
MontgomeryDivisorDataPool.Shared.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
ulong result = PrimeOrderCalculator.Calculate(
prime: 13UL,
previousOrder: null,
MontgomeryDivisorDataPool.Shared.FromModulus(13UL),
config,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculatorAccelerator.Return(gpu);
result.Should().Be(expectedOrder);
}
result.Should().Be(ComputeOrderByDoubling(13UL));
}
[Fact]
[Trait("Category", "Fast")]
public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored()
{
var config = new PrimeOrderCalculator.PrimeOrderSearchConfig(
smallFactorLimit: 1,
pollardRhoMilliseconds: 0,
maxPowChecks: 8,
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
[Fact]
[Trait("Category", "Fast")]
public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode()
{
var heuristicConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
smallFactorLimit: 2,
pollardRhoMilliseconds: 0,
maxPowChecks: 1,
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
ulong result = PrimeOrderCalculator.Calculate(
gpu,
prime: 13UL,
previousOrder: null,
MontgomeryDivisorDataPool.Shared.FromModulus(13UL),
config,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(239UL);
ulong heuristic = PrimeOrderCalculator.Calculate(
prime: 239UL,
previousOrder: null,
divisorData,
heuristicConfig,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculatorAccelerator.Return(gpu);
result.Should().Be(ComputeOrderByDoubling(13UL));
}
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
[Fact]
[Trait("Category", "Fast")]
public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode()
{
var heuristicConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
smallFactorLimit: 2,
pollardRhoMilliseconds: 0,
maxPowChecks: 1,
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
smallFactorLimit: 2,
pollardRhoMilliseconds: 0,
maxPowChecks: 1,
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(239UL);
ulong heuristic = PrimeOrderCalculator.Calculate(
gpu,
prime: 239UL,
previousOrder: null,
divisorData,
heuristicConfig,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
ulong strict = PrimeOrderCalculator.Calculate(
prime: 239UL,
previousOrder: null,
divisorData,
strictConfig,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
strict.Should().Be(heuristic);
}
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
smallFactorLimit: 2,
pollardRhoMilliseconds: 0,
maxPowChecks: 1,
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
[Fact]
[Trait("Category", "Fast")]
public void Calculate_handles_128_bit_prime()
{
UInt128 prime = UInt128.Parse("18446744073709641691");
ulong strict = PrimeOrderCalculator.Calculate(
gpu,
prime: 239UL,
previousOrder: null,
divisorData,
strictConfig,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
UInt128 result = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
strict.Should().Be(heuristic);
result.Should().Be((UInt128)1229782938247309446UL);
}
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void GetCycle_uses_wide_prime_heuristic()
{
UInt128 prime = UInt128.Parse("18446744073709641691");
MersenneDivisorCycles.GetCycle(prime).Should().Be((UInt128)1229782938247309446UL);
}
[Fact]
[Trait("Category", "Fast")]
public void Calculate_handles_128_bit_prime()
{
UInt128 prime = UInt128.Parse("18446744073709641691");
private static ulong ComputeOrderByDoubling(ulong prime)
{
if (prime == 2UL)
{
return 1UL;
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
UInt128 result = PrimeOrderCalculator.Calculate(
gpu,
prime,
previousOrder: null,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
if (prime == 3UL)
{
return 2UL;
}
PrimeOrderCalculatorAccelerator.Return(gpu);
result.Should().Be((UInt128)1229782938247309446UL);
}
ulong order = 1UL;
ulong value = 2UL % prime;
[Fact]
[Trait("Category", "Fast")]
public void GetCycle_uses_wide_prime_heuristic()
{
UInt128 prime = UInt128.Parse("18446744073709641691");
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
var mersenneDivisorCycles = MersenneDivisorCycles.GetCycle(gpu, prime).Should().Be((UInt128)1229782938247309446UL);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
while (value != 1UL)
{
value = (value * 2UL) % prime;
order++;
}
private static ulong ComputeOrderByDoubling(ulong prime)
{
if (prime == 2UL)
{
return 1UL;
}
return order;
}
if (prime == 3UL)
{
return 2UL;
}
ulong order = 1UL;
ulong value = 2UL % prime;
while (value != 1UL)
{
value = (value * 2UL) % prime;
order++;
}
return order;
}
}
@@ -1,4 +1,5 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
@@ -6,20 +7,28 @@ namespace PerfectNumbers.Core.Tests;
[Trait("Category", "Fast")]
public class PrimeTesterGcdTests
{
[Theory]
[InlineData(81UL, true)]
[InlineData(101UL, false)]
public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected)
{
PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected);
}
[Theory]
[InlineData(81UL, true)]
[InlineData(101UL, false)]
public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected)
{
PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected);
}
[Fact]
public void SharesFactorWithMaxExponentBatch_filters_values()
{
ulong[] values = [81UL, 101UL];
Span<byte> results = stackalloc byte[values.Length];
PrimeTester.SharesFactorWithMaxExponentBatch(values, results);
results.ToArray().Should().Equal(new byte[] { 1, 0 });
}
[Fact]
public void SharesFactorWithMaxExponentBatch_filters_values()
{
ulong[] values = [81UL, 101UL];
Span<byte> results = stackalloc byte[values.Length];
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
PrimeTester.SharesFactorWithMaxExponentBatch(gpu, values, results);
results.ToArray().Should().Equal(new byte[] { 1, 0 });
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,81 +1,98 @@
using FluentAssertions;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class PrimeTesterIsPrimeTests
{
[Theory]
[Trait("Category", "Fast")]
// EvenPerfectBitScanner never routes the tiny values anymore; keep coverage focused on the admissible range handled by production scans so the guards in PrimeTester stay commented out without breaking validation.
[InlineData(7UL, true)]
[InlineData(9UL, false)]
[InlineData(11UL, true)]
[InlineData(13UL, true)]
[InlineData(21UL, false)]
[InlineData(97UL, true)]
[InlineData(133UL, false)]
[InlineData(137UL, true)]
[InlineData(143UL, false)]
[InlineData(199UL, true)]
[InlineData(209UL, false)]
[InlineData(403UL, false)]
[InlineData(341UL, false)]
public void IsPrime_returns_expected_results(ulong n, bool expected)
{
var tester = new PrimeTester();
[Theory]
[Trait("Category", "Fast")]
// EvenPerfectBitScanner never routes the tiny values anymore; keep coverage focused on the admissible range handled by production scans so the guards in PrimeTester stay commented out without breaking validation.
[InlineData(7UL, true)]
[InlineData(9UL, false)]
[InlineData(11UL, true)]
[InlineData(13UL, true)]
[InlineData(21UL, false)]
[InlineData(97UL, true)]
[InlineData(133UL, false)]
[InlineData(137UL, true)]
[InlineData(143UL, false)]
[InlineData(199UL, true)]
[InlineData(209UL, false)]
[InlineData(403UL, false)]
[InlineData(341UL, false)]
public void IsPrime_returns_expected_results(ulong n, bool expected)
{
var tester = new PrimeTester();
PrimeTester.IsPrime(n).Should().Be(expected);
}
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(139UL, true)]
[InlineData(189UL, false)]
[InlineData(193UL, true)]
[InlineData(279UL, false)]
public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected)
{
var tester = new PrimeTester();
[Theory]
[Trait("Category", "Fast")]
[InlineData(139UL, true)]
[InlineData(189UL, false)]
[InlineData(193UL, true)]
[InlineData(279UL, false)]
public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected)
{
var tester = new PrimeTester();
PrimeTester.IsPrimeGpu(n).Should().Be(expected);
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
PrimeTester.IsPrimeGpu(gpu, n).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
{
var tester = new PrimeTester();
PrimeTester.IsPrime(137UL).Should().BeTrue();
PrimeTester.IsPrime(341UL).Should().BeFalse();
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
{
var tester = new PrimeTester();
PrimeTester.IsPrime(137UL).Should().BeTrue();
PrimeTester.IsPrime(341UL).Should().BeFalse();
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
{
GpuPrimeWorkLimiter.SetLimit(64);
PrimeTester.GpuBatchSize = 256_000;
[Fact]
[Trait("Category", "Fast")]
public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
{
GpuPrimeWorkLimiter.SetLimit(64);
PrimeTester.GpuBatchSize = 256_000;
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
var results = new bool[primes.Length];
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
var results = new bool[primes.Length];
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
Parallel.For(0, primes.Length, i =>
{
results[i] = PrimeTester.IsPrimeGpu(primes[i]);
});
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
Parallel.For(0, primes.Length, i =>
{
results[i] = PrimeTester.IsPrimeGpu(gpu, primes[i]);
});
results.Should().AllBeEquivalentTo(true);
results.Should().AllBeEquivalentTo(true);
Parallel.For(0, Environment.ProcessorCount * 16, _ =>
{
foreach (ulong prime in primes)
{
PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
}
});
}
Parallel.For(0, Environment.ProcessorCount * 16, _ =>
{
foreach (ulong prime in primes)
{
PrimeTester.IsPrimeGpu(gpu, prime).Should().BeTrue();
}
});
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
}
@@ -1,284 +1,293 @@
using System.Numerics;
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class UInt128ExtensionsTests
{
public static IEnumerable<object[]> BinaryGcdCases()
{
yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 };
yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 };
public static IEnumerable<object[]> BinaryGcdCases()
{
yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 };
yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 };
UInt128 big1 = (UInt128.One << 96) + 12345;
UInt128 big2 = (UInt128.One << 80) + 67890;
yield return new object[]
{
big1,
big2,
(UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2)
};
}
UInt128 big1 = (UInt128.One << 96) + 12345;
UInt128 big2 = (UInt128.One << 80) + 67890;
yield return new object[]
{
big1,
big2,
(UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2)
};
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(BinaryGcdCases))]
public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected)
{
a.BinaryGcd(b).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(BinaryGcdCases))]
public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected)
{
a.BinaryGcd(b).Should().Be(expected);
}
public static IEnumerable<object[]> TrailingZeroCases()
{
yield return new object[] { UInt128.Zero, 128 };
yield return new object[] { (UInt128)1, 0 };
yield return new object[] { (UInt128)8, 3 };
yield return new object[] { UInt128.One << 80, 80 };
}
public static IEnumerable<object[]> TrailingZeroCases()
{
yield return new object[] { UInt128.Zero, 128 };
yield return new object[] { (UInt128)1, 0 };
yield return new object[] { (UInt128)8, 3 };
yield return new object[] { UInt128.One << 80, 80 };
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(TrailingZeroCases))]
public void CountTrailingZeros_returns_expected(UInt128 value, int expected)
{
value.CountTrailingZeros().Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[MemberData(nameof(TrailingZeroCases))]
public void CountTrailingZeros_returns_expected(UInt128 value, int expected)
{
value.CountTrailingZeros().Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(3UL)]
[InlineData(5UL)]
[InlineData(7UL)]
public void CalculateOrder_matches_naive(ulong q)
{
UInt128 q128 = q;
ulong expected = NaiveOrder(q);
q128.CalculateOrder().Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(3UL)]
[InlineData(5UL)]
[InlineData(7UL)]
public void CalculateOrder_matches_naive(ulong q)
{
UInt128 q128 = q;
ulong expected = NaiveOrder(q);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
q128.CalculateOrder(gpu).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
private static ulong NaiveOrder(ulong q)
{
if (q <= 2UL)
{
return 0UL;
}
private static ulong NaiveOrder(ulong q)
{
if (q <= 2UL)
{
return 0UL;
}
ulong order = 1UL;
ulong value = 2UL % q;
while (value != 1UL)
{
value = (value << 1) % q;
order++;
}
ulong order = 1UL;
ulong value = 2UL % q;
while (value != 1UL)
{
value = (value << 1) % q;
order++;
}
return order;
}
return order;
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, true)]
[InlineData(9UL, false)]
[InlineData(97UL, true)]
public void IsPrimeCandidate_checks_small(ulong n, bool expected)
{
((UInt128)n).IsPrimeCandidate().Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, true)]
[InlineData(9UL, false)]
[InlineData(97UL, true)]
public void IsPrimeCandidate_checks_small(ulong n, bool expected)
{
((UInt128)n).IsPrimeCandidate().Should().Be(expected);
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrimeCandidate_detects_large_composite()
{
UInt128 n = (UInt128.One << 64) + 1;
n.IsPrimeCandidate().Should().BeFalse();
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrimeCandidate_detects_large_composite()
{
UInt128 n = (UInt128.One << 64) + 1;
n.IsPrimeCandidate().Should().BeFalse();
}
[Fact]
[Trait("Category", "Fast")]
public void Mod10_8_5_3_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 12345678901234567890UL;
[Fact]
[Trait("Category", "Fast")]
public void Mod10_8_5_3_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 12345678901234567890UL;
value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
ulong expected10 = (ulong)(value % 10);
ulong expected8 = (ulong)(value % 8);
ulong expected5 = (ulong)(value % 5);
ulong expected3 = (ulong)(value % 3);
ulong expected10 = (ulong)(value % 10);
ulong expected8 = (ulong)(value % 8);
ulong expected5 = (ulong)(value % 5);
ulong expected3 = (ulong)(value % 3);
mod10.Should().Be(expected10);
mod8.Should().Be(expected8);
mod5.Should().Be(expected5);
mod3.Should().Be(expected3);
}
mod10.Should().Be(expected10);
mod8.Should().Be(expected8);
mod5.Should().Be(expected5);
mod3.Should().Be(expected3);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 0UL)]
[InlineData(2UL, 1UL)]
[InlineData(2UL, 63UL)]
[InlineData(2UL, 127UL)]
[InlineData(3UL, 5UL)]
public void Pow_matches_BigInteger(ulong value, ulong exponent)
{
UInt128 v = value;
UInt128 expected = (UInt128)BigPow(value, exponent);
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 0UL)]
[InlineData(2UL, 1UL)]
[InlineData(2UL, 63UL)]
[InlineData(2UL, 127UL)]
[InlineData(3UL, 5UL)]
public void Pow_matches_BigInteger(ulong value, ulong exponent)
{
UInt128 v = value;
UInt128 expected = (UInt128)BigPow(value, exponent);
v.Pow(exponent).Should().Be(expected);
}
v.Pow(exponent).Should().Be(expected);
}
private static BigInteger BigPow(ulong value, ulong exponent)
{
BigInteger result = BigInteger.One;
BigInteger baseValue = value;
private static BigInteger BigPow(ulong value, ulong exponent)
{
BigInteger result = BigInteger.One;
BigInteger baseValue = value;
while (exponent != 0UL)
{
if ((exponent & 1UL) != 0UL)
{
result *= baseValue;
}
while (exponent != 0UL)
{
if ((exponent & 1UL) != 0UL)
{
result *= baseValue;
}
baseValue *= baseValue;
exponent >>= 1;
}
baseValue *= baseValue;
exponent >>= 1;
}
return result;
}
return result;
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 10UL, 17UL)]
[InlineData(123456789UL, 3UL, 97UL)]
public void ModPow_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
{
UInt128 v = value;
UInt128 e = exponent;
UInt128 m = modulus;
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 10UL, 17UL)]
[InlineData(123456789UL, 3UL, 97UL)]
public void ModPow_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
{
UInt128 v = value;
UInt128 e = exponent;
UInt128 m = modulus;
UInt128 expected = (UInt128)BigInteger.ModPow(value, exponent, modulus);
UInt128 expected = (UInt128)BigInteger.ModPow(value, exponent, modulus);
v.ModPow(e, m).Should().Be(expected);
}
v.ModPow(e, m).Should().Be(expected);
}
[Fact]
[Trait("Category", "Fast")]
public void ModPow_handles_large_values()
{
UInt128 v = (UInt128.One << 96) + 12345;
UInt128 e = 17;
UInt128 m = 97;
[Fact]
[Trait("Category", "Fast")]
public void ModPow_handles_large_values()
{
UInt128 v = (UInt128.One << 96) + 12345;
UInt128 e = 17;
UInt128 m = 97;
UInt128 expected = (UInt128)BigInteger.ModPow((BigInteger)v, (BigInteger)e, (BigInteger)m);
UInt128 expected = (UInt128)BigInteger.ModPow((BigInteger)v, (BigInteger)e, (BigInteger)m);
v.ModPow(e, m).Should().Be(expected);
}
v.ModPow(e, m).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL)]
[InlineData(1UL)]
[InlineData(123456789UL)]
public void Mod10_matches_operator(ulong low)
{
UInt128 value = (UInt128.One << 100) + low;
value.Mod10().Should().Be((ulong)(value % 10));
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL)]
[InlineData(1UL)]
[InlineData(123456789UL)]
public void Mod10_matches_operator(ulong low)
{
UInt128 value = (UInt128.One << 100) + low;
value.Mod10().Should().Be((ulong)(value % 10));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod8_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 12345;
value.Mod8().Should().Be((ulong)(value % 8));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod8_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 12345;
value.Mod8().Should().Be((ulong)(value % 8));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod3_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 12345;
value.Mod3().Should().Be((ulong)(value % 3));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod3_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 12345;
value.Mod3().Should().Be((ulong)(value % 3));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod6_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 9876543210UL;
value.Mod6().Should().Be((ulong)(value % 6));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod6_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 9876543210UL;
value.Mod6().Should().Be((ulong)(value % 6));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod5_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 12345;
value.Mod5().Should().Be((ulong)(value % 5));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod5_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 12345;
value.Mod5().Should().Be((ulong)(value % 5));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod7_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 9876543210987654321UL;
value.Mod7().Should().Be((ulong)(value % 7));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod7_matches_operator()
{
UInt128 value = (UInt128.One << 100) + 9876543210987654321UL;
value.Mod7().Should().Be((ulong)(value % 7));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod11_matches_operator()
{
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
value.Mod11().Should().Be((ulong)(value % 11));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod11_matches_operator()
{
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
value.Mod11().Should().Be((ulong)(value % 11));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod128_matches_operator()
{
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
value.Mod128().Should().Be((ulong)(value % 128));
}
[Fact]
[Trait("Category", "Fast")]
public void Mod128_matches_operator()
{
UInt128 value = (UInt128.One << 120) + 9876543210987654321UL;
value.Mod128().Should().Be((ulong)(value % 128));
}
[Fact]
[Trait("Category", "Fast")]
public void Mul64_matches_expected_product()
{
UInt128 a = (UInt128.One << 80) + 123456789;
UInt128 b = (UInt128.One << 64) + 987654321;
[Fact]
[Trait("Category", "Fast")]
public void Mul64_matches_expected_product()
{
UInt128 a = (UInt128.One << 80) + 123456789;
UInt128 b = (UInt128.One << 64) + 987654321;
UInt128 expected = (UInt128)((UInt128)(ulong)a * b);
UInt128 expected = (UInt128)((UInt128)(ulong)a * b);
a.Mul64(b).Should().Be(expected);
}
a.Mul64(b).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(123UL, 456UL, 789UL)]
[InlineData(123456789UL, 987654321UL, 1009UL)]
public void MulMod_matches_BigInteger(ulong a, ulong b, ulong modulus)
{
UInt128 ua = a % modulus;
UInt128 ub = b % modulus;
UInt128 mod = modulus;
[Theory]
[Trait("Category", "Fast")]
[InlineData(123UL, 456UL, 789UL)]
[InlineData(123456789UL, 987654321UL, 1009UL)]
public void MulMod_matches_BigInteger(ulong a, ulong b, ulong modulus)
{
UInt128 ua = a % modulus;
UInt128 ub = b % modulus;
UInt128 mod = modulus;
UInt128 expected = (UInt128)(((BigInteger)ua * (BigInteger)ub) % modulus);
UInt128 expected = (UInt128)(((BigInteger)ua * (BigInteger)ub) % modulus);
ua.MulMod(ub, mod).Should().Be(expected);
}
ua.MulMod(ub, mod).Should().Be(expected);
}
[Fact]
[Trait("Category", "Fast")]
public void MulMod_handles_large_inputs()
{
UInt128 a = (UInt128.One << 80) + 12345;
UInt128 b = (UInt128.One << 96) + 67890;
UInt128 mod = (UInt128.One << 100) - 93;
[Fact]
[Trait("Category", "Fast")]
public void MulMod_handles_large_inputs()
{
UInt128 a = (UInt128.One << 80) + 12345;
UInt128 b = (UInt128.One << 96) + 67890;
UInt128 mod = (UInt128.One << 100) - 93;
UInt128 expected = (UInt128)(((BigInteger)a * (BigInteger)b) % (BigInteger)mod);
UInt128 expected = (UInt128)(((BigInteger)a * (BigInteger)b) % (BigInteger)mod);
a.MulMod(b, mod).Should().Be(expected);
}
a.MulMod(b, mod).Should().Be(expected);
}
}
+208 -183
View File
@@ -1,225 +1,250 @@
using System.Numerics;
using FluentAssertions;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class ULongExtensionsTests
{
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL)]
[InlineData(1UL)]
[InlineData(9UL)]
[InlineData(10UL)]
[InlineData(1234567890UL)]
[InlineData(ulong.MaxValue)]
public void Mod10_returns_same_as_operator(ulong value)
{
ulong expected = value % 10UL;
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL)]
[InlineData(1UL)]
[InlineData(9UL)]
[InlineData(10UL)]
[InlineData(1234567890UL)]
[InlineData(ulong.MaxValue)]
public void Mod10_returns_same_as_operator(ulong value)
{
ulong expected = value % 10UL;
ULongExtensions.Mod10(value).Should().Be(expected);
}
ULongExtensions.Mod10(value).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL)]
[InlineData(ulong.MaxValue)]
[InlineData(1234567890123456789UL)]
public void Mod128_matches_operator(ulong value)
{
value.Mod128().Should().Be(value % 128UL);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL)]
[InlineData(ulong.MaxValue)]
[InlineData(1234567890123456789UL)]
public void Mod128_matches_operator(ulong value)
{
value.Mod128().Should().Be(value % 128UL);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(1UL)]
[InlineData(123456789UL)]
[InlineData(ulong.MaxValue)]
public void Mod10_8_5_3Steps_matches_manual_formula(ulong value)
{
value.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
[Theory]
[Trait("Category", "Fast")]
[InlineData(1UL)]
[InlineData(123456789UL)]
[InlineData(ulong.MaxValue)]
public void Mod10_8_5_3Steps_matches_manual_formula(ulong value)
{
value.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
ulong expected10 = ((value % 10UL) << 1) % 10UL;
ulong expected8 = ((value & 7UL) << 1) & 7UL;
ulong expected5 = ((value % 5UL) << 1) % 5UL;
ulong expected3 = ((value % 3UL) << 1) % 3UL;
ulong expected10 = ((value % 10UL) << 1) % 10UL;
ulong expected8 = ((value & 7UL) << 1) & 7UL;
ulong expected5 = ((value % 5UL) << 1) % 5UL;
ulong expected3 = ((value % 3UL) << 1) % 3UL;
step10.Should().Be(expected10);
step8.Should().Be(expected8);
step5.Should().Be(expected5);
step3.Should().Be(expected3);
}
step10.Should().Be(expected10);
step8.Should().Be(expected8);
step5.Should().Be(expected5);
step3.Should().Be(expected3);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, true)]
[InlineData(9UL, false)]
[InlineData(97UL, true)]
public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected)
{
candidate.IsPrimeCandidate().Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, true)]
[InlineData(9UL, false)]
[InlineData(97UL, true)]
public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected)
{
candidate.IsPrimeCandidate().Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(3UL)]
[InlineData(5UL)]
[InlineData(7UL)]
[InlineData(11UL)]
public void CalculateOrder_matches_naive_algorithm(ulong q)
{
ulong expected = NaiveOrder(q);
[Theory]
[Trait("Category", "Fast")]
[InlineData(3UL)]
[InlineData(5UL)]
[InlineData(7UL)]
[InlineData(11UL)]
public void CalculateOrder_matches_naive_algorithm(ulong q)
{
ulong expected = NaiveOrder(q);
q.CalculateOrder().Should().Be(expected);
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
q.CalculateOrder(gpu).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 10UL, 17UL)]
[InlineData(123456789UL, 3UL, 97UL)]
public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
{
ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus);
[Theory]
[Trait("Category", "Fast")]
[InlineData(2UL, 10UL, 17UL)]
[InlineData(123456789UL, 3UL, 97UL)]
public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus)
{
ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus);
value.ModPow64(exponent, modulus).Should().Be(expected);
}
value.ModPow64(exponent, modulus).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(10UL, 17UL)]
[InlineData(123UL, 97UL)]
public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
[Theory]
[Trait("Category", "Fast")]
[InlineData(10UL, 17UL)]
[InlineData(123UL, 97UL)]
public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
exponent.PowMod((UInt128)modulus).Should().Be(expected);
}
exponent.PowMod((UInt128)modulus).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(5UL, 7UL, 3UL, 4UL)]
[InlineData(10UL, 7UL, 3UL, 2UL)]
public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected)
{
((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(5UL, 7UL, 3UL, 4UL)]
[InlineData(10UL, 7UL, 3UL, 2UL)]
public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected)
{
((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(15UL, true)]
[InlineData(7UL, false)]
[InlineData(9UL, false)]
public void SharesFactorWithExponentMinusOne_detects_relationship(ulong exponent, bool expected)
{
exponent.SharesFactorWithExponentMinusOne().Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(15UL, true)]
[InlineData(7UL, false)]
[InlineData(9UL, false)]
public void SharesFactorWithExponentMinusOne_detects_relationship(ulong exponent, bool expected)
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
exponent.SharesFactorWithExponentMinusOne(gpu).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(1234567890123456789UL, 3UL)]
[InlineData(ulong.MaxValue, 7UL)]
public void FastDiv64_matches_division(ulong value, ulong divisor)
{
ulong mul = (ulong)(((UInt128)1 << 64) / divisor);
[Theory]
[Trait("Category", "Fast")]
[InlineData(1234567890123456789UL, 3UL)]
[InlineData(ulong.MaxValue, 7UL)]
public void FastDiv64_matches_division(ulong value, ulong divisor)
{
ulong mul = (ulong)(((UInt128)1 << 64) / divisor);
value.FastDiv64(divisor, mul).Should().Be(value / divisor);
}
value.FastDiv64(divisor, mul).Should().Be(value / divisor);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL, 0UL)]
[InlineData(ulong.MaxValue, 1UL)]
[InlineData(123456789UL, 987654321UL)]
public void MulHighCpu_matches_high_bits(ulong x, ulong y)
{
ulong expected = (ulong)(((UInt128)x * y) >> 64);
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL, 0UL)]
[InlineData(ulong.MaxValue, 1UL)]
[InlineData(123456789UL, 987654321UL)]
public void MulHighCpu_matches_high_bits(ulong x, ulong y)
{
ulong expected = (ulong)(((UInt128)x * y) >> 64);
x.MulHighCpu(y).Should().Be(expected);
}
x.MulHighCpu(y).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL, 0UL)]
[InlineData(ulong.MaxValue, 1UL)]
[InlineData(123456789UL, 987654321UL)]
public void MulHighGpu_matches_cpu_baseline(ulong x, ulong y)
{
x.MulHighGpu(y).Should().Be(x.MulHighCpu(y));
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL, 0UL)]
[InlineData(ulong.MaxValue, 1UL)]
[InlineData(123456789UL, 987654321UL)]
public void MulHighGpu_matches_cpu_baseline(ulong x, ulong y)
{
x.MulHighGpu(y).Should().Be(x.MulHighCpu(y));
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL, 0UL)]
[InlineData(ulong.MaxValue, 2UL)]
[InlineData(123456789UL, 987654321UL)]
public void Mul64_matches_UInt128_product(ulong a, ulong b)
{
UInt128 expected = (UInt128)a * b;
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL, 0UL)]
[InlineData(ulong.MaxValue, 2UL)]
[InlineData(123456789UL, 987654321UL)]
public void Mul64_matches_UInt128_product(ulong a, ulong b)
{
UInt128 expected = (UInt128)a * b;
a.Mul64(b).Should().Be(expected);
}
a.Mul64(b).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(123456789UL, 987654321UL, 97UL)]
[InlineData(ulong.MaxValue, ulong.MaxValue, 1009UL)]
public void MulMod64_matches_BigInteger(ulong a, ulong b, ulong modulus)
{
ulong expected = (ulong)BigInteger.Remainder((BigInteger)a * b, modulus);
[Theory]
[Trait("Category", "Fast")]
[InlineData(123456789UL, 987654321UL, 97UL)]
[InlineData(ulong.MaxValue, ulong.MaxValue, 1009UL)]
public void MulMod64_matches_BigInteger(ulong a, ulong b, ulong modulus)
{
ulong expected = (ulong)BigInteger.Remainder((BigInteger)a * b, modulus);
a.MulMod64(b, modulus).Should().Be(expected);
}
a.MulMod64(b, modulus).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(100UL, 7UL, 3UL)]
[InlineData(123UL, 9UL, 6UL)]
public void PowModWithCycle_ulong_overload_uses_cycle(ulong exponent, ulong modulus, ulong cycle)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
[Theory]
[Trait("Category", "Fast")]
[InlineData(100UL, 7UL, 3UL)]
[InlineData(123UL, 9UL, 6UL)]
public void PowModWithCycle_ulong_overload_uses_cycle(ulong exponent, ulong modulus, ulong cycle)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
exponent.PowModWithCycle((UInt128)modulus, cycle).Should().Be(expected);
}
exponent.PowModWithCycle((UInt128)modulus, cycle).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(100UL, 7UL, 3UL)]
[InlineData(123UL, 9UL, 6UL)]
public void PowModWithCycle_ulong_uint128_cycle_uses_cycle(ulong exponent, ulong modulus, ulong cycle)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
[Theory]
[Trait("Category", "Fast")]
[InlineData(100UL, 7UL, 3UL)]
[InlineData(123UL, 9UL, 6UL)]
public void PowModWithCycle_ulong_uint128_cycle_uses_cycle(ulong exponent, ulong modulus, ulong cycle)
{
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
exponent.PowModWithCycle((UInt128)modulus, (UInt128)cycle).Should().Be(expected);
}
exponent.PowModWithCycle((UInt128)modulus, (UInt128)cycle).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(11UL, 13UL)]
public void PowModCrt_matches_BigInteger(ulong exponent, ulong modulus)
{
MersenneDivisorCycles cycles = MersenneDivisorCycles.Shared;
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
[Theory]
[Trait("Category", "Fast")]
[InlineData(11UL, 13UL)]
public void PowModCrt_matches_BigInteger(ulong exponent, ulong modulus)
{
MersenneDivisorCycles cycles = MersenneDivisorCycles.Shared;
UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus);
exponent.PowModCrt((UInt128)modulus, cycles).Should().Be(expected);
}
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try
{
exponent.PowModCrt(gpu, (UInt128)modulus, cycles).Should().Be(expected);
}
finally
{
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
private static ulong NaiveOrder(ulong q)
{
if (q <= 2UL)
{
return 0UL;
}
private static ulong NaiveOrder(ulong q)
{
if (q <= 2UL)
{
return 0UL;
}
ulong order = 1UL;
ulong value = 2UL % q;
while (value != 1UL)
{
value = (value << 1) % q;
order++;
}
ulong order = 1UL;
ulong value = 2UL % q;
while (value != 1UL)
{
value = (value << 1) % q;
order++;
}
return order;
}
return order;
}
}
@@ -1,11 +1,14 @@
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu;
internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelerator gpu) : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{
public void Reset()
public void Reset()
{
}
// TODO: Convert this to static method
public void CheckDivisor(
ulong divisor,
in MontgomeryDivisorData divisorData,
@@ -29,7 +32,7 @@ internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDi
if (divisorCycle == 0UL)
{
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(gpu, divisor);
if (divisorCycle == 0UL)
{
// DivisorCycleCache guarantees a positive cycle for divisors greater than one.
@@ -4,7 +4,7 @@ using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberDivisorByDivisorTester
{
private ulong _divisorLimit;
private int _batchSize = 1_024;
@@ -42,7 +42,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor)
{
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
@@ -58,6 +58,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
bool processedAll;
bool composite = CheckDivisors(
gpu,
prime,
allowedMax,
out processedAll,
@@ -84,7 +85,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
}
}
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
{
MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession();
if (session is not null)
@@ -92,16 +93,16 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return session;
}
return new MersenneCpuDivisorScanSession();
return new MersenneCpuDivisorScanSession(gpu);
}
private bool CheckDivisors(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
ulong allowedMax,
out bool processedAll,
out ulong foundDivisor)
{
foundDivisor = 0UL;
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
// if (allowedMax < 3UL)
@@ -171,6 +172,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
remainder11 = (byte)(divisorLow % 11UL);
return CheckDivisors64(
gpu,
prime,
stepLow,
limit.Low,
@@ -203,7 +205,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL);
remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
var divisorPool = MontgomeryDivisorDataPool.Shared;
while (divisor.CompareTo(limit) <= 0)
{
@@ -225,6 +226,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
gpu,
candidate,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
@@ -241,7 +243,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
foundDivisor = candidate;
processedAll = true;
PrimeOrderCalculatorAccelerator.Return(gpu);
return true;
}
@@ -259,13 +260,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
remainder11 = AddMod11(remainder11, step11);
}
PrimeOrderCalculatorAccelerator.Return(gpu);
processedAll = true;
foundDivisor = 0UL;
return false;
}
private static bool CheckDivisors64(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
ulong step,
ulong limit,
@@ -285,7 +286,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
out ulong foundDivisor)
{
bool canAdvance = step <= limit;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
if (!canAdvance)
{
@@ -304,6 +304,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
gpu,
divisor,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
@@ -319,7 +320,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{
foundDivisor = divisor;
processedAll = true;
PrimeOrderCalculatorAccelerator.Return(gpu);
return true;
}
@@ -332,7 +332,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
processedAll = true;
foundDivisor = 0UL;
PrimeOrderCalculatorAccelerator.Return(gpu);
return false;
}
@@ -340,7 +339,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{
processedAll = true;
foundDivisor = 0UL;
PrimeOrderCalculatorAccelerator.Return(gpu);
return false;
}
@@ -361,6 +359,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
gpu,
divisor,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
@@ -375,7 +374,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{
foundDivisor = divisor;
processedAll = true;
PrimeOrderCalculatorAccelerator.Return(gpu);
return true;
}
@@ -390,7 +388,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{
processedAll = true;
foundDivisor = 0UL;
PrimeOrderCalculatorAccelerator.Return(gpu);
return false;
}
@@ -1,90 +1,92 @@
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu;
public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType)
{
private static readonly MersenneDivisorCycles _cycles = MersenneDivisorCycles.Shared;
private readonly GpuKernelType _kernelType = kernelType;
private static readonly MersenneDivisorCycles _cycles = MersenneDivisorCycles.Shared;
private readonly GpuKernelType _kernelType = kernelType;
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
UInt128 k = UInt128.One;
UInt128 one = k;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
// Use residue automaton always on CPU
var auto = new MersenneResidueAutomaton(exponent);
public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
UInt128 k = UInt128.One;
UInt128 one = k;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
// Use residue automaton always on CPU
var auto = new MersenneResidueAutomaton(exponent);
bool reject;
bool shouldCheck;
UInt128 q = auto.CurrentQ();
UInt128 divPow;
UInt128 halfPow;
UInt128 phi;
UInt128 qCycle;
bool reject;
bool shouldCheck;
UInt128 q = auto.CurrentQ();
UInt128 divPow;
UInt128 halfPow;
UInt128 phi;
UInt128 qCycle;
ulong div;
ulong mod8;
ulong phi64;
ulong div;
ulong mod8;
ulong phi64;
while (k <= maxK && Volatile.Read(ref isPrime))
{
// TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block
// and compute missing cycles on the configured device without queuing additional block generation.
qCycle = MersenneDivisorCycles.GetCycle(q);
shouldCheck = auto.Mod10R != 5UL;
if (shouldCheck)
{
mod8 = auto.Mod8R;
if (mod8 != 1UL && mod8 != 7UL)
{
shouldCheck = false;
}
else if (auto.Mod3R == 0UL || auto.Mod5R == 0UL)
{
// TODO: Swap these Mod3/Mod5 zero checks to the cached residue tables once the automaton exposes
// the benchmarked lookup-based helpers so CPU scans avoid runtime modulo instructions.
shouldCheck = false;
}
}
while (k <= maxK && Volatile.Read(ref isPrime))
{
// TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block
// and compute missing cycles on the configured device without queuing additional block generation.
qCycle = MersenneDivisorCycles.GetCycle(gpu, q);
shouldCheck = auto.Mod10R != 5UL;
if (shouldCheck)
{
mod8 = auto.Mod8R;
if (mod8 != 1UL && mod8 != 7UL)
{
shouldCheck = false;
}
else if (auto.Mod3R == 0UL || auto.Mod5R == 0UL)
{
// TODO: Swap these Mod3/Mod5 zero checks to the cached residue tables once the automaton exposes
// the benchmarked lookup-based helpers so CPU scans avoid runtime modulo instructions.
shouldCheck = false;
}
}
if (shouldCheck)
{
if (_kernelType == GpuKernelType.Pow2Mod)
{
reject = exponent.PowModWithCycle(q, qCycle) == UInt128.One;
}
else
{
phi = q - one;
reject = false;
if (phi <= ulong.MaxValue)
{
phi64 = (ulong)phi;
if (phi64.PowModWithCycle(q, qCycle) == one)
{
halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - one;
if (halfPow.BinaryGcd(q) == one)
{
div = phi64.FastDiv64(exponent, divMul);
divPow = div.PowModWithCycle(q, qCycle) - one;
if (divPow.BinaryGcd(q) == one)
{
reject = true;
}
}
}
}
}
if (shouldCheck)
{
if (_kernelType == GpuKernelType.Pow2Mod)
{
reject = exponent.PowModWithCycle(q, qCycle) == UInt128.One;
}
else
{
phi = q - one;
reject = false;
if (phi <= ulong.MaxValue)
{
phi64 = (ulong)phi;
if (phi64.PowModWithCycle(q, qCycle) == one)
{
halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - one;
if (halfPow.BinaryGcd(q) == one)
{
div = phi64.FastDiv64(exponent, divMul);
divPow = div.PowModWithCycle(q, qCycle) - one;
if (divPow.BinaryGcd(q) == one)
{
reject = true;
}
}
}
}
}
if (reject && q.IsPrimeCandidate())
{
Volatile.Write(ref isPrime, false);
break;
}
}
if (reject && q.IsPrimeCandidate())
{
Volatile.Write(ref isPrime, false);
break;
}
}
k += 1UL;
auto.Next();
q = auto.CurrentQ();
}
}
k += 1UL;
auto.Next();
q = auto.CurrentQ();
}
}
}
@@ -1,44 +1,46 @@
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core;
public class MersenneNumberLucasLehmerCpuTester
{
public bool IsPrime(ulong exponent)
{
// Early rejections aligned with incremental/order sieves, but safe for small p:
// - If 3 | p and p != 3, then 7 | M_p -> composite.
// - If p ≡ 1 (mod 4) and p shares a factor with (p-1), reject fast.
// - If p is divisible by divisors specific to numbers ending with 1 or 7, reject fast.
// TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering
// shares the benchmarked bitmask implementations and avoids slow modulo instructions in the hot path.
if (
((exponent % 3UL) == 0UL && exponent != 3UL) ||
((exponent % 5UL) == 0UL && exponent != 5UL) ||
((exponent % 7UL) == 0UL && exponent != 7UL) ||
((exponent % 11UL) == 0UL && exponent != 11UL)
)
{
return false;
}
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent)
{
// Early rejections aligned with incremental/order sieves, but safe for small p:
// - If 3 | p and p != 3, then 7 | M_p -> composite.
// - If p ≡ 1 (mod 4) and p shares a factor with (p-1), reject fast.
// - If p is divisible by divisors specific to numbers ending with 1 or 7, reject fast.
// TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering
// shares the benchmarked bitmask implementations and avoids slow modulo instructions in the hot path.
if (
((exponent % 3UL) == 0UL && exponent != 3UL) ||
((exponent % 5UL) == 0UL && exponent != 5UL) ||
((exponent % 7UL) == 0UL && exponent != 7UL) ||
((exponent % 11UL) == 0UL && exponent != 11UL)
)
{
return false;
}
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
{
return false;
}
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu))
{
return false;
}
UInt128 s = UInt128Numbers.Four, two = UInt128Numbers.Two,
m = (UInt128.One << (int)exponent) - UInt128.One;
UInt128 s = UInt128Numbers.Four, two = UInt128Numbers.Two,
m = (UInt128.One << (int)exponent) - UInt128.One;
UInt128 limit = exponent - UInt128Numbers.Two;
for (UInt128 i = UInt128.Zero; i < limit; i++)
{
// Lucas–Lehmer squares the evolving residue, so we stay on the Montgomery powmod path rather than the pow2-specific helper.
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(m)) - two;
if (s < UInt128.Zero)
{
s += m;
}
}
UInt128 limit = exponent - UInt128Numbers.Two;
for (UInt128 i = UInt128.Zero; i < limit; i++)
{
// Lucas–Lehmer squares the evolving residue, so we stay on the Montgomery powmod path rather than the pow2-specific helper.
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(gpu, m)) - two;
if (s < UInt128.Zero)
{
s += m;
}
}
return s == UInt128.Zero;
}
return s == UInt128.Zero;
}
}
@@ -1,10 +1,12 @@
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu;
public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
{
private readonly GpuKernelType _kernelType = kernelType;
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
UInt128 k = 1UL;
var auto = new MersenneResidueAutomaton(exponent);
@@ -13,10 +15,10 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
while (k <= maxK && Volatile.Read(ref isPrime))
{
qCycle = MersenneDivisorCycles.GetCycle(q);
// TODO: When this lookup misses the snapshot, invoke the configured device to compute the
// single required cycle on demand without persisting it so the CPU order path retains the
// cycle-stepping speedups while honoring the no-extra-cache constraint for large divisors.
qCycle = MersenneDivisorCycles.GetCycle(gpu, q);
// TODO: When this lookup misses the snapshot, invoke the configured device to compute the
// single required cycle on demand without persisting it so the CPU order path retains the
// cycle-stepping speedups while honoring the no-extra-cache constraint for large divisors.
int allowMask2 = lastDigit == LastDigit.Seven ? CpuConstants.LastSevenMask10 : CpuConstants.LastOneMask10;
bool shouldCheck = ((allowMask2 >> (int)auto.Mod10R) & 1) != 0;
if (shouldCheck)
@@ -33,33 +35,33 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
}
if (shouldCheck)
{
if (_kernelType == GpuKernelType.Pow2Mod)
{
if (exponent.PowModWithCycle(q, qCycle) == 1UL)
{
Volatile.Write(ref isPrime, false);
break;
}
}
else
{
UInt128 phi = q - 1UL;
if (phi <= ulong.MaxValue)
{
ulong phi64 = (ulong)phi;
if (phi64.PowModWithCycle(q, qCycle) == 1UL)
{
UInt128 halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - 1UL;
if (halfPow.BinaryGcd(q) == 1UL)
{
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
ulong div = phi64.FastDiv64(exponent, divMul);
UInt128 divPow = div.PowModWithCycle(q, qCycle) - 1UL;
if (divPow.BinaryGcd(q) == 1UL)
{
Volatile.Write(ref isPrime, false);
break;
}
if (_kernelType == GpuKernelType.Pow2Mod)
{
if (exponent.PowModWithCycle(q, qCycle) == 1UL)
{
Volatile.Write(ref isPrime, false);
break;
}
}
else
{
UInt128 phi = q - 1UL;
if (phi <= ulong.MaxValue)
{
ulong phi64 = (ulong)phi;
if (phi64.PowModWithCycle(q, qCycle) == 1UL)
{
UInt128 halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - 1UL;
if (halfPow.BinaryGcd(q) == 1UL)
{
ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
ulong div = phi64.FastDiv64(exponent, divMul);
UInt128 divPow = div.PowModWithCycle(q, qCycle) - 1UL;
if (divPow.BinaryGcd(q) == 1UL)
{
Volatile.Write(ref isPrime, false);
break;
}
}
}
}
@@ -1,3 +1,5 @@
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu;
public class MersenneNumberResidueCpuTester
@@ -5,7 +7,7 @@ public class MersenneNumberResidueCpuTester
private ModResidueTracker? _mersenneResidueTracker;
// CPU residue variant using tracker + unrolled residue updates (no method calls in the loop).
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
// Initialize/update Mersenne residue tracker and start a merge walk for ascending q divisors
@@ -92,7 +94,7 @@ public class MersenneNumberResidueCpuTester
// (and below) so every qualifying q reuses precomputed lengths instead of recomputing via
// tracker.MergeOrAppend.
// cycle-based quick check for small q
if (localIsPrime && tracker.MergeOrAppend(exponent, q, out isDivider) && isDivider &&
if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, q, out isDivider) && isDivider &&
(q <= ulong.MaxValue ? ((ulong)q).IsPrimeCandidate() : q.IsPrimeCandidate()))
{
localIsPrime = false;
@@ -105,7 +107,7 @@ public class MersenneNumberResidueCpuTester
((allowMask >> (int)r10_1) & 1) != 0 && (r8_1 == 1UL || r8_1 == 7UL) && r3_1 != 0UL && r5_1 != 0UL &&
localIsPrime)
{
if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider &&
if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider &&
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
{
localIsPrime = false;
@@ -118,7 +120,7 @@ public class MersenneNumberResidueCpuTester
((allowMask >> (int)r10_2) & 1) != 0 && (r8_2 == 1UL || r8_2 == 7UL) && r3_2 != 0UL && r5_2 != 0UL &&
localIsPrime)
{
if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider &&
if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider &&
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
{
localIsPrime = false;
@@ -131,7 +133,7 @@ public class MersenneNumberResidueCpuTester
((allowMask >> (int)r10_3) & 1) != 0 && (r8_3 == 1UL || r8_3 == 7UL) && r3_3 != 0UL && r5_3 != 0UL &&
localIsPrime)
{
if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider &&
if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider &&
(qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate()))
{
localIsPrime = false;
+7 -7
View File
@@ -57,13 +57,13 @@ public sealed class DivisorCycleCache
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
}
public ulong GetCycleLength(ulong divisor, bool skipPrimeOrderHeuristic = false)
public ulong GetCycleLength(PrimeOrderCalculatorAccelerator gpu, ulong divisor, bool skipPrimeOrderHeuristic = false)
{
if (!skipPrimeOrderHeuristic)
{
Span<ulong> result = stackalloc ulong[1];
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
GetCycleLengths(singleDivisor, result);
GetCycleLengths(gpu, singleDivisor, result);
return result[0];
}
@@ -90,12 +90,12 @@ public sealed class DivisorCycleCache
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
cycleLength = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: true);
cycleLength = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true);
divisorPool.Return(divisorData);
return cycleLength;
}
public void GetCycleLengths(ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
public void GetCycleLengths(PrimeOrderCalculatorAccelerator gpu,ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
{
ulong[] snapshot = _snapshot;
int length = divisors.Length;
@@ -139,7 +139,7 @@ public sealed class DivisorCycleCache
}
else
{
ComputeCyclesCpu(divisors, cycles, missingIndices);
ComputeCyclesCpu(gpu, divisors, cycles, missingIndices);
}
}
@@ -187,7 +187,7 @@ public sealed class DivisorCycleCache
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
private static void ComputeCyclesCpu(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
{
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
for (int i = 0; i < indices.Length; i++)
@@ -195,7 +195,7 @@ public sealed class DivisorCycleCache
int targetIndex = indices[i];
ulong divisor = divisors[targetIndex];
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData);
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData);
divisorPool.Return(divisorData);
}
}
@@ -20,7 +20,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
private ulong _divisorLimit;
// private bool _isConfigured;
private readonly ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>[] _resourcePool = [..AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>())];
private readonly ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>())];
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = [];
@@ -50,9 +50,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
// _isConfigured = true;
}
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor)
{
ulong allowedMax;
int batchCapacity;
@@ -87,7 +85,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
// GpuPrimeWorkLimiter();
// int acceleratorIndex = AcceleratorPool.Shared.Rent();
// var accelerator = _accelerators[acceleratorIndex];
var gpu = PrimeOrderCalculatorAccelerator.Rent(batchCapacity);
var acceleratorIndex = gpu.AcceleratorIndex;
// Monitor.Enter(gpuLease.ExecutionLock);
@@ -117,7 +114,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
out processedCount);
ReturnBatchResources(acceleratorIndex, resources);
PrimeOrderCalculatorAccelerator.Return(gpu);
// Monitor.Exit(gpuLease.ExecutionLock);
// GpuPrimeWorkLimiter.Release();
@@ -364,7 +360,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
{
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
{
return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
return MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
}
return computedCycle;
@@ -481,8 +477,8 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{
private readonly MersenneNumberDivisorByDivisorGpuTester _owner;
private readonly PrimeOrderCalculatorAccelerator _primeTesterAccelerator;
private readonly Accelerator _accelerator;
private readonly PrimeOrderCalculatorAccelerator _primeOrderCalculatorAccelerator;
private readonly AcceleratorStream _stream;
private Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!;
@@ -496,15 +492,36 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
private int _capacity;
internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner)
internal DivisorScanSession(PrimeOrderCalculatorAccelerator gpu, MersenneNumberDivisorByDivisorGpuTester owner)
{
_owner = owner;
// GpuPrimeWorkLimiter.Acquire();
_primeTesterAccelerator = PrimeOrderCalculatorAccelerator.Rent(1);
Accelerator accelerator = _primeTesterAccelerator.Accelerator;
_primeOrderCalculatorAccelerator = gpu;
Accelerator accelerator = gpu.Accelerator;
_accelerator = accelerator;
_stream = accelerator.CreateStream();
_capacity = Math.Max(1, owner._gpuBatchSize);
// lock (accelerator)
{
_divisorBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(1);
_offsetBuffer = accelerator.Allocate1D<int>(1);
_countBuffer = accelerator.Allocate1D<int>(1);
_cycleBuffer = accelerator.Allocate1D<ulong>(1);
_firstHitBuffer = accelerator.Allocate1D<int>(1);
}
int desiredCapacity = PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount;
_capacity = desiredCapacity;
// lock (accelerator)
{
_exponentsBuffer = accelerator.Allocate1D<ulong>(desiredCapacity);
_hitBuffer = accelerator.Allocate1D<byte>(desiredCapacity);
}
_hostBuffer = ThreadStaticPools.UlongPool.Rent(desiredCapacity);
_kernel = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>>();
}
internal void Reset()
@@ -513,45 +530,16 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
private void EnsureExecutionResourcesLocked(int requiredCapacity)
{
Accelerator accelerator = _accelerator;
if (_kernel == null)
if (requiredCapacity > _capacity)
{
_kernel = KernelUtil.GetKernel(_accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>>();
}
_exponentsBuffer.Dispose();
_hitBuffer.Dispose();
ThreadStaticPools.UlongPool.Return(_hostBuffer, clearArray: false);
if (_divisorBuffer == null)
{
// lock (accelerator)
{
_divisorBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(1);
_offsetBuffer = accelerator.Allocate1D<int>(1);
_countBuffer = accelerator.Allocate1D<int>(1);
_cycleBuffer = accelerator.Allocate1D<ulong>(1);
_firstHitBuffer = accelerator.Allocate1D<int>(1);
}
}
bool allocateBuffers = _exponentsBuffer == null;
if (!allocateBuffers && _capacity < requiredCapacity)
{
_exponentsBuffer?.Dispose();
_hitBuffer?.Dispose();
if (_hostBuffer != null)
{
ThreadStaticPools.UlongPool.Return(_hostBuffer, clearArray: false);
_hostBuffer = null!;
}
_exponentsBuffer = null!;
_hitBuffer = null!;
allocateBuffers = true;
}
if (allocateBuffers)
{
Accelerator accelerator = _accelerator;
int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity;
_capacity = desiredCapacity;
// lock (accelerator)
{
_exponentsBuffer = accelerator.Allocate1D<ulong>(desiredCapacity);
@@ -581,13 +569,12 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
ulong cycle = divisorCycle;
ulong firstPrime = primes[0];
var gpu = _primeOrderCalculatorAccelerator;
if (cycle == 0UL)
{
var gpu = _primeTesterAccelerator;
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
{
cycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
}
else
{
@@ -714,7 +701,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit);
}
public DivisorScanSession CreateDivisorSession()
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
{
// The tester is configured once at startup and the session pool relies on thread-safe collections, so the previous synchronization guard stays commented out.
// lock (_sync)
@@ -739,8 +726,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
return session;
}
return new DivisorScanSession(this);
return new DivisorScanSession(gpu, this);
}
IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession();
}
@@ -1,5 +1,6 @@
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu;
@@ -86,7 +87,7 @@ public sealed class MersenneNumberDivisorGpuTester
private static (ulong divisor, uint cycle)[]? _divisorCandidates = [];
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
{
ReadOnlyGpuUInt128 readOnlyDivisor;
@@ -142,7 +143,7 @@ public sealed class MersenneNumberDivisorGpuTester
continue;
}
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(gpu, d);
if ((UInt128)p % cycle128 != UInt128.Zero)
{
continue;
@@ -2,6 +2,7 @@ using System.Buffers;
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
using UInt128 = System.UInt128;
namespace PerfectNumbers.Core.Gpu;
@@ -131,7 +132,7 @@ public class MersenneNumberLucasLehmerGpuTester
// Configurable LL slice size to keep kernels short. Default 32.
public int SliceSize = 32;
public bool IsPrime(ulong exponent, bool runOnGpu)
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent, bool runOnGpu)
{
// Early rejections aligned with incremental/order sieves, but safe for small p:
// - If 3 | p and p != 3, then 7 | M_p -> composite.
@@ -142,7 +143,7 @@ public class MersenneNumberLucasLehmerGpuTester
return false;
}
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu))
{
return false;
}
@@ -8,12 +8,11 @@ namespace PerfectNumbers.Core.Gpu;
public static class Pow2MontgomeryGpuCalculator
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKeep(ulong exponent, in MontgomeryDivisorData divisor)
public static ulong CalculateKeep(PrimeOrderCalculatorAccelerator gpu, ulong exponent, in MontgomeryDivisorData divisor)
{
// GpuPrimeWorkLimiter.Acquire();
ulong result = 0UL;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
int acceleratorIndex = gpu.AcceleratorIndex;
Accelerator? accelerator = gpu.Accelerator;
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer = gpu.Input;
@@ -32,37 +31,6 @@ public static class Pow2MontgomeryGpuCalculator
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
PrimeOrderCalculatorAccelerator.Return(gpu);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateConvert(ulong exponent, in MontgomeryDivisorData divisor)
{
// GpuPrimeWorkLimiter.Acquire();
ulong result = 0UL;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer = gpu.Input;
MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = gpu.OutputUlong;
int acceleratorIndex = gpu.AcceleratorIndex;
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1);
// We don't need to worry about any left-overs here.
// resultBuffer.MemSetToZero(stream);
var kernelLauncher = gpu.ConvertToStandardKernelLauncher!;
kernelLauncher(stream, 1, exponentBuffer.View, divisor.Modulus, divisor.NPrime, divisor.MontgomeryOne, divisor.MontgomeryTwo, divisor.MontgomeryTwoSquared, resultBuffer.View);
resultBuffer.View.CopyToCPU(stream, ref result, 1);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
PrimeOrderCalculatorAccelerator.Return(gpu);
return result;
}
@@ -243,7 +243,7 @@ internal static partial class PrimeOrderGpuHeuristics
}
}
public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder, in MontgomeryDivisorData divisorData)
public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder, in MontgomeryDivisorData? divisorData)
{
Span<ulong> exponents = stackalloc ulong[1];
Span<ulong> remainders = stackalloc ulong[1];
@@ -254,7 +254,7 @@ internal static partial class PrimeOrderGpuHeuristics
return status;
}
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> remainders, in MontgomeryDivisorData divisorData)
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> remainders, in MontgomeryDivisorData? divisorData)
{
// This will never occur in production code
// if (exponents.Length == 0)
@@ -309,20 +309,20 @@ internal static partial class PrimeOrderGpuHeuristics
return computed ? GpuPow2ModStatus.Success : GpuPow2ModStatus.Unavailable;
}
public static GpuPow2ModStatus TryPow2Mod(in UInt128 exponent, in UInt128 prime, out UInt128 remainder)
public static GpuPow2ModStatus TryPow2Mod(PrimeOrderCalculatorAccelerator gpu, in UInt128 exponent, in UInt128 prime, out UInt128 remainder)
{
Span<UInt128> exponents = stackalloc UInt128[1];
Span<UInt128> remainders = stackalloc UInt128[1];
exponents[0] = exponent;
GpuPow2ModStatus status = TryPow2ModBatch(exponents, prime, remainders);
GpuPow2ModStatus status = TryPow2ModBatch(gpu, exponents, prime, remainders);
remainder = remainders[0];
return status;
}
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> remainders)
public static GpuPow2ModStatus TryPow2ModBatch(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> remainders)
{
return TryPow2ModBatchInternal(exponents, prime, remainders);
return TryPow2ModBatchInternal(gpu, exponents, prime, remainders);
}
internal static bool TryCalculateOrder(
@@ -471,7 +471,7 @@ internal static partial class PrimeOrderGpuHeuristics
return kernel;
}
private static GpuPow2ModStatus TryPow2ModBatchInternal(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> remainders)
private static GpuPow2ModStatus TryPow2ModBatchInternal(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> remainders)
{
if (exponents.Length == 0)
{
@@ -509,11 +509,11 @@ internal static partial class PrimeOrderGpuHeuristics
}
}
ComputeOnGpuWide(exponents, prime, target);
ComputeOnGpuWide(gpu, exponents, prime, target);
return GpuPow2ModStatus.Success;
}
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, MontgomeryDivisorData? divisorData, Span<ulong> results)
{
// GpuPrimeWorkLimiter.Acquire();
int acceleratorIndex = AcceleratorPool.Shared.Rent();
@@ -540,6 +540,11 @@ internal static partial class PrimeOrderGpuHeuristics
try
{
if (divisorData is null)
{
divisorData = MontgomeryDivisorData.Empty;
}
kernelLauncher(stream, exponents.Length, exponentBuffer.View, divisorData.Modulus, divisorData.NPrime, divisorData.MontgomeryOne, divisorData.MontgomeryTwo, divisorData.MontgomeryTwoSquared, remainderBuffer.View);
remainderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(results), exponents.Length);
@@ -573,14 +578,13 @@ internal static partial class PrimeOrderGpuHeuristics
}
}
private static void ComputeOnGpuWide(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> results)
private static void ComputeOnGpuWide(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> results)
{
int length = exponents.Length;
GpuUInt128[]? rentedExponents = null;
GpuUInt128[]? rentedResults = null;
ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
PrimeOrderCalculatorAccelerator? gpu = null;
bool poolingRequired = length > WideStackThreshold;
Span<GpuUInt128> exponentSpan = poolingRequired
? stackalloc GpuUInt128[length]
@@ -596,7 +600,6 @@ internal static partial class PrimeOrderGpuHeuristics
}
GpuUInt128 modulus = (GpuUInt128)prime;
gpu = PrimeOrderCalculatorAccelerator.Rent(1);
int acceleratorIndex = gpu.AcceleratorIndex;
Accelerator accelerator = gpu.Accelerator;
@@ -625,7 +628,6 @@ internal static partial class PrimeOrderGpuHeuristics
remainderBuffer.Dispose();
exponentBuffer.Dispose();
PrimeOrderCalculatorAccelerator.Return(gpu);
if (rentedExponents is not null)
{
@@ -168,15 +168,15 @@ public sealed class HeuristicCombinedPrimeTester
return HeuristicTrialDivisionCpu(n, maxDivisorSquare, nMod10);
}
public static bool IsPrimeGpu(ulong n)
{
byte nMod10 = (byte)n.Mod10();
ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
bool isPrime = IsPrimeGpu(gpu, n, maxDivisorSquare, nMod10);
PrimeOrderCalculatorAccelerator.Return(gpu);
return isPrime;
}
// public static bool IsPrimeGpu(ulong n)
// {
// byte nMod10 = (byte)n.Mod10();
// ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
// var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
// bool isPrime = IsPrimeGpu(gpu, n, maxDivisorSquare, nMod10);
// PrimeOrderCalculatorAccelerator.Return(gpu);
// return isPrime;
// }
public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n)
{
@@ -520,7 +520,7 @@ public sealed class HeuristicCombinedPrimeTester
}
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
cycleFromHint = false;
cycleComputed = resolvedCycle != 0UL;
return resolvedCycle;
+7 -9
View File
@@ -83,17 +83,17 @@ public sealed class HeuristicPrimeTester
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPrimeGpu(ulong n)
public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n)
{
byte nMod10 = (byte)n.Mod10();
ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
return IsPrimeGpu(n, maxDivisorSquare, nMod10);
return IsPrimeGpu(gpu, n, maxDivisorSquare, nMod10);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPrimeGpu(ulong n, ulong maxDivisorSquare, byte nMod10) => HeuristicIsPrimeGpuCore(n, maxDivisorSquare, nMod10);
public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n, ulong maxDivisorSquare, byte nMod10) => HeuristicIsPrimeGpuCore(gpu, n, maxDivisorSquare, nMod10);
private static bool HeuristicIsPrimeGpuCore(ulong n, ulong maxDivisorSquare, byte nMod10)
private static bool HeuristicIsPrimeGpuCore(PrimeOrderCalculatorAccelerator gpu, ulong n, ulong maxDivisorSquare, byte nMod10)
{
// TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner?
if (maxDivisorSquare < 9UL)
@@ -106,7 +106,7 @@ public sealed class HeuristicPrimeTester
// return HeuristicTrialDivisionCpu(n, maxDivisorSquare, nMod10, includeGroupB: false);
// }
bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, maxDivisorSquare, nMod10);
bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(gpu, n, maxDivisorSquare, nMod10);
return !compositeDetected;
}
@@ -239,10 +239,9 @@ public sealed class HeuristicPrimeTester
}
private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10)
private static bool HeuristicTrialDivisionGpuDetectsDivisor(PrimeOrderCalculatorAccelerator gpu, ulong n, ulong maxDivisorSquare, byte nMod10)
{
// GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
int acceleratorIndex = gpu.AcceleratorIndex;
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
var flagView1D = gpu.OutputInt!.View;
@@ -313,7 +312,6 @@ public sealed class HeuristicPrimeTester
// }
AcceleratorStreamPool.Return(acceleratorIndex, stream);
PrimeOrderCalculatorAccelerator.Return(gpu);
// GpuPrimeWorkLimiter.Release();
return compositeDetected;
}
@@ -487,7 +485,7 @@ public sealed class HeuristicPrimeTester
}
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
cycleFromHint = false;
cycleComputed = resolvedCycle != 0UL;
return resolvedCycle;
@@ -1,3 +1,5 @@
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core;
public interface IMersenneNumberDivisorByDivisorTester
@@ -10,9 +12,9 @@ public interface IMersenneNumberDivisorByDivisorTester
ulong GetAllowedMaxDivisor(ulong prime);
bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor);
bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor);
IDivisorScanSession CreateDivisorSession();
IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu);
void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
+14 -9
View File
@@ -75,7 +75,7 @@ public class MersenneDivisorCycles
}
}
if (TryCalculateCycleLengthHeuristic(divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
if (TryCalculateCycleLengthHeuristic(gpu, divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
{
return heuristicCycle == exponent;
}
@@ -163,7 +163,7 @@ public class MersenneDivisorCycles
}
}
public ulong GetCycle(ulong divisor, in MontgomeryDivisorData divisorData)
public ulong GetCycle(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData)
{
// Fast-path: in-memory array for small divisors
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
@@ -208,10 +208,10 @@ public class MersenneDivisorCycles
// TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
// configuration (and skip persisting the result) when the divisor falls outside the in-memory snapshot
// so we respect the large-p memory limits and avoid extra cache locks.
return CalculateCycleLength(divisor, divisorData);
return CalculateCycleLength(gpu, divisor, divisorData);
}
public static UInt128 GetCycle(UInt128 divisor)
public static UInt128 GetCycle(PrimeOrderCalculatorAccelerator gpu, UInt128 divisor)
{
// For divisor = 2^k, cycle is 1
if ((divisor & (divisor - UInt128.One)) == UInt128.Zero)
@@ -224,12 +224,13 @@ public class MersenneDivisorCycles
ulong divisor64 = (ulong)divisor;
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor64);
ulong cycle = CalculateCycleLength(divisor64, divisorData);
ulong cycle = CalculateCycleLength(gpu, divisor64, divisorData);
divisorPool.Return(divisorData);
return (UInt128)cycle;
}
UInt128 wideOrder = PrimeOrderCalculator.Calculate(
gpu,
divisor,
previousOrder: null,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
@@ -285,6 +286,7 @@ public class MersenneDivisorCycles
// }
ulong computedOrder = PrimeOrderCalculator.Calculate(
gpu,
divisor,
previousOrder: null,
divisorData,
@@ -743,6 +745,7 @@ public class MersenneDivisorCycles
// (ulong divisor, ulong cycle)[] localCycles = ArrayPool<(ulong, ulong)>.Shared.Rent(checked((int)rangeLength));
int localCycleIndex = 0;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (ulong divisor = localStart; divisor <= localEnd; divisor++)
{
if ((divisor & 1UL) == 0UL)
@@ -762,11 +765,12 @@ public class MersenneDivisorCycles
// unrolled-hex calculator so the CPU generator matches
// the GPU benchmark leader for large divisors.
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
ulong cycle = CalculateCycleLength(divisor, divisorData);
ulong cycle = CalculateCycleLength(gpu, divisor, divisorData);
divisorPool.Return(divisorData);
localCycles[localCycleIndex++] = (divisor, cycle);
}
PrimeOrderCalculatorAccelerator.Return(gpu);
if (localCycleIndex == 0)
{
ThreadStaticPools.Return(localCycles);
@@ -929,9 +933,9 @@ public class MersenneDivisorCycles
public static ulong CalculateCycleLengthGpu(ulong divisor) => DivisorCycleKernels.CalculateCycleLengthGpu(divisor);
public static ulong CalculateCycleLength(ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
public static ulong CalculateCycleLength(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
{
if (TryCalculateCycleLengthHeuristic(divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
if (TryCalculateCycleLengthHeuristic(gpu, divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
return cycleLength;
return CalculateCycleLengthFallback(divisor);
@@ -940,7 +944,7 @@ public class MersenneDivisorCycles
// [ThreadStatic]
// private static HeuristicPrimeTester? _primeTester;
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
internal static bool TryCalculateCycleLengthHeuristic(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
{
if ((divisor & (divisor - 1UL)) == 0UL)
{
@@ -966,6 +970,7 @@ public class MersenneDivisorCycles
// if (!skipPrimeOrderHeuristic && HeuristicCombinedPrimeTester.IsPrimeGpu(divisor))
{
ulong computedOrder = PrimeOrderCalculator.Calculate(
gpu,
divisor,
previousOrder: null,
divisorData,
@@ -239,10 +239,10 @@ public static class MersenneNumberDivisorByDivisorTester
int chunkSize = primesPerTask <= 0 ? 1 : primesPerTask;
void ProcessPrime(ulong prime)
void ProcessPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime)
{
Console.WriteLine($"Processing {prime}");
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted, out ulong divisor);
bool isPrime = tester.IsPrime(gpu, prime, out bool divisorsExhausted, out ulong divisor);
if (!isPrime)
{
@@ -259,10 +259,13 @@ public static class MersenneNumberDivisorByDivisorTester
if (workerCount == 1)
{
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
foreach (ulong prime in filteredPrimes)
{
ProcessPrime(prime);
ProcessPrime(gpu, prime);
}
PrimeOrderCalculatorAccelerator.Return(gpu);
}
else
{
@@ -295,11 +298,13 @@ public static class MersenneNumberDivisorByDivisorTester
startGate.Wait();
Console.WriteLine($"Task started for range {rangeStart}");
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (int index = rangeStart; index < rangeEnd; index++)
{
ProcessPrime(filteredPrimes[index]);
ProcessPrime(gpu, filteredPrimes[index]);
}
PrimeOrderCalculatorAccelerator.Return(gpu);
Console.WriteLine($"Task finished for range {rangeStart}");
PrimeOrderCalculatorAccelerator.DisposeAll();
},
+13 -10
View File
@@ -63,6 +63,7 @@ public sealed class MersenneNumberTester(
UInt128 twoP = (UInt128)exponent << 1; // 2 * p
LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
// CPU path: compute orders directly and cache
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
if (!_useGpuOrder)
{
UInt128 k = UInt128.One;
@@ -97,10 +98,11 @@ public sealed class MersenneNumberTester(
continue;
}
ulong ord = q.CalculateOrder();
ulong ord = q.CalculateOrder(gpu);
_orderCache[q] = ord;
}
PrimeOrderCalculatorAccelerator.Return(gpu);
return;
}
@@ -190,7 +192,7 @@ public sealed class MersenneNumberTester(
ulong order = orders[i];
if (order == 0UL)
{
order = qs[offset + i].CalculateOrder();
order = qs[offset + i].CalculateOrder(gpu);
}
if (useOrderCache)
@@ -205,11 +207,12 @@ public sealed class MersenneNumberTester(
}
AcceleratorStreamPool.Return(acceleratorIndex,stream);
PrimeOrderCalculatorAccelerator.Return(gpu);
// GpuPrimeWorkLimiter.Release();
uInt128Pool.Return(qs);
}
public bool IsMersennePrime(ulong exponent)
public bool IsMersennePrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent)
{
// Safe early rejections using known orders for tiny primes:
// 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime.
@@ -220,7 +223,7 @@ public sealed class MersenneNumberTester(
return false;
}
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu))
{
return false;
}
@@ -251,7 +254,7 @@ public sealed class MersenneNumberTester(
}
else
{
_residueCpuTester!.Scan(exponent, twoP, lastDigit, maxK, ref prePrime);
_residueCpuTester!.Scan(gpu, exponent, twoP, lastDigit, maxK, ref prePrime);
}
return prePrime;
@@ -270,7 +273,7 @@ public sealed class MersenneNumberTester(
}
else
{
_incrementalCpuTester.Scan(exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime);
_incrementalCpuTester.Scan(gpu, exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime);
}
if (!prePrime)
@@ -279,8 +282,8 @@ public sealed class MersenneNumberTester(
}
return _useGpuLucas
? _lucasLehmerGpuTester.IsPrime(exponent, runOnGpu: true)
: _lucasLehmerCpuTester.IsPrime(exponent);
? _lucasLehmerGpuTester.IsPrime(gpu, exponent, runOnGpu: true)
: _lucasLehmerCpuTester.IsPrime(gpu, exponent);
}
bool isPrime = true;
@@ -293,7 +296,7 @@ public sealed class MersenneNumberTester(
}
else
{
_orderCpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime);
_orderCpuTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref isPrime);
}
}
else
@@ -304,7 +307,7 @@ public sealed class MersenneNumberTester(
}
else
{
_incrementalCpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime);
_incrementalCpuTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref isPrime);
}
}
+8 -8
View File
@@ -1,5 +1,5 @@
using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Gpu.Accelerators;
using UInt128 = System.UInt128;
namespace PerfectNumbers.Core;
@@ -39,7 +39,7 @@ public sealed class ModResidueTracker
/// (number must be non-decreasing across calls). Adds and initializes new
/// divisors on first use.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsDivisible(UInt128 number, UInt128 divisor)
public bool IsDivisible(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
{
if (divisor == UInt128.Zero)
{
@@ -52,7 +52,7 @@ public sealed class ModResidueTracker
int idx = LowerBound(divisors, divisor);
if (idx == divisors.Count || divisors[idx] != divisor)
{
UInt128 residue = ComputeInitialResidue(number, divisor);
UInt128 residue = ComputeInitialResidue(gpu, number, divisor);
divisors.Insert(idx, divisor);
_residues.Insert(idx, residue);
if (_hasLastAppended && idx == divisors.Count - 1 && divisor >= _lastAppended)
@@ -81,7 +81,7 @@ public sealed class ModResidueTracker
// Merge or append current candidate divisor (ascending order required across calls).
// Returns true and sets 'divisible' based on residue at 'number'.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MergeOrAppend(UInt128 number, UInt128 divisor, out bool divisible)
public bool MergeOrAppend(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor, out bool divisible)
{
// Assume number is non-decreasing; advance residues lazily when number changes.
List<UInt128> divisors = _divisors;
@@ -97,7 +97,7 @@ public sealed class ModResidueTracker
List<UInt128> residues = _residues;
if (divisors.Count == 0 || (_hasLastAppended && divisor >= _lastAppended && divisors[^1] <= divisor))
{
residue = ComputeInitialResidue(number, divisor);
residue = ComputeInitialResidue(gpu, number, divisor);
divisors.Add(divisor);
residues.Add(residue);
_hasLastAppended = true;
@@ -123,7 +123,7 @@ public sealed class ModResidueTracker
// Insert at cursor to keep order
residue = ComputeInitialResidue(number, divisor);
residue = ComputeInitialResidue(gpu, number, divisor);
divisors.Insert(cursor, divisor);
residues.Insert(cursor, residue);
divisible = residue == UInt128.Zero;
@@ -238,7 +238,7 @@ public sealed class ModResidueTracker
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private UInt128 ComputeInitialResidue(UInt128 number, UInt128 divisor)
private UInt128 ComputeInitialResidue(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
{
if (_model == ResidueModel.Identity)
{
@@ -266,7 +266,7 @@ public sealed class ModResidueTracker
if (pow != UInt128.One)
return (pow - UInt128.One) % divisor;
ulong ord = divisor.CalculateOrder();
ulong ord = divisor.CalculateOrder(gpu);
if (ord == 0UL || number % (UInt128)ord != UInt128.Zero)
return UInt128.One; // does NOT divide
@@ -44,7 +44,7 @@ internal static partial class PrimeOrderCalculator
}
}
private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
private static ulong CalculateInternal(PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
{
// TODO: Is this condition ever met on EvenPerfectBitScanner's execution path? If not, we can add a clarification comment and comment out the entire block. We want to support p candidates at least greater or equal to 31.
if (prime <= 3UL)
@@ -54,10 +54,8 @@ internal static partial class PrimeOrderCalculator
ulong phi = prime - 1UL;
PrimeOrderCalculatorAccelerator gpu = PrimeOrderCalculatorAccelerator.Rent(1);
if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(gpu, prime, previousOrder, config, divisorData, out ulong gpuOrder))
{
PrimeOrderCalculatorAccelerator.Return(gpu);
return gpuOrder;
}
@@ -73,13 +71,11 @@ internal static partial class PrimeOrderCalculator
// result = CalculateByFactorizationCpu(gpu, prime, divisorData);
phiFactors.Dispose();
PrimeOrderCalculatorAccelerator.Return(gpu);
return result;
}
result = RunHeuristicPipelineCpu(gpu, prime, previousOrder, config, divisorData, phi, phiFactors);
phiFactors.Dispose();
PrimeOrderCalculatorAccelerator.Return(gpu);
return result;
}
@@ -2,6 +2,7 @@ using System.Buffers;
using System.Diagnostics;
using System.Numerics;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core;
@@ -82,7 +83,7 @@ internal static partial class PrimeOrderCalculator
Console.WriteLine(messageFactory());
}
}
private static UInt128 CalculateWideInternal(in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config)
private static UInt128 CalculateWideInternal(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config)
{
if (prime <= UInt128.One)
{
@@ -100,11 +101,11 @@ internal static partial class PrimeOrderCalculator
UInt128 result;
if (phiFactors.Factors is null)
{
result = FinishStrictlyWide(prime, divisorData);
result = FinishStrictlyWide(gpu, prime, divisorData);
}
else
{
result = RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
result = RunHeuristicPipelineWide(gpu, prime, previousOrder, divisorData, config, phi, phiFactors);
}
debugScope.Dispose();
@@ -112,32 +113,33 @@ internal static partial class PrimeOrderCalculator
}
private static UInt128 RunHeuristicPipelineWide(
PrimeOrderCalculatorAccelerator gpu,
in UInt128 prime,
in UInt128? previousOrder,
in MontgomeryDivisorData divisorData,
in MontgomeryDivisorData? divisorData,
in PrimeOrderSearchConfig config,
in UInt128 phi,
in PartialFactorResult128 phiFactors)
{
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
if (phiFactors.FullyFactored && TrySpecialMaxWide(gpu, phi, prime, divisorData, phiFactors))
{
return phi;
}
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
candidateOrder = ExponentLoweringWide(candidateOrder, prime, divisorData, phiFactors);
UInt128 candidateOrder = InitializeStartingOrderWide(gpu, prime, phi, divisorData);
candidateOrder = ExponentLoweringWide(gpu, candidateOrder, prime, divisorData, phiFactors);
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
if (TryConfirmOrderWide(gpu, prime, candidateOrder, divisorData, config))
{
return candidateOrder;
}
if (config.Mode == PrimeOrderMode.Strict)
{
return FinishStrictlyWide(prime, divisorData);
return FinishStrictlyWide(gpu, prime, divisorData);
}
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
if (TryHeuristicFinishWide(gpu, prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
{
return order;
}
@@ -145,12 +147,12 @@ internal static partial class PrimeOrderCalculator
return candidateOrder;
}
private static UInt128 FinishStrictlyWide(in UInt128 prime, in MontgomeryDivisorData divisorData)
private static UInt128 FinishStrictlyWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in MontgomeryDivisorData? divisorData)
{
return CalculateByFactorizationWide(prime, divisorData);
return CalculateByFactorizationWide(gpu, prime, divisorData);
}
private static bool TrySpecialMaxWide(in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
private static bool TrySpecialMaxWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData? divisorData, in PartialFactorResult128 factors)
{
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
int length = factors.Count;
@@ -158,7 +160,7 @@ internal static partial class PrimeOrderCalculator
{
UInt128 factor = factorSpan[i].Value;
UInt128 reduced = phi / factor;
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One)
{
return false;
}
@@ -167,14 +169,14 @@ internal static partial class PrimeOrderCalculator
return true;
}
private static UInt128 InitializeStartingOrderWide(in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData divisorData)
private static UInt128 InitializeStartingOrderWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData? divisorData)
{
UInt128 order = phi;
UInt128 mod8 = prime & (UInt128)7UL;
if (mod8 == UInt128.One || mod8 == (UInt128)7UL)
{
UInt128 half = phi >> 1;
if (Pow2ModWide(half, prime, divisorData) == UInt128.One)
if (Pow2ModWide(gpu, half, prime, divisorData) == UInt128.One)
{
order = half;
}
@@ -183,7 +185,7 @@ internal static partial class PrimeOrderCalculator
return order;
}
private static UInt128 ExponentLoweringWide(UInt128 order, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
private static UInt128 ExponentLoweringWide(PrimeOrderCalculatorAccelerator gpu, UInt128 order, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
{
ArrayPool<FactorEntry128> pool = ThreadStaticPools.FactorEntry128Pool;
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
@@ -215,7 +217,7 @@ internal static partial class PrimeOrderCalculator
if ((order % primeFactor) == UInt128.Zero)
{
UInt128 reduced = order / primeFactor;
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One)
{
order = reduced;
continue;
@@ -230,14 +232,14 @@ internal static partial class PrimeOrderCalculator
return order;
}
private static bool TryConfirmOrderWide(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
private static bool TryConfirmOrderWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
{
if (order == UInt128.Zero)
{
return false;
}
if (Pow2ModWide(order, prime, divisorData) != UInt128.One)
if (Pow2ModWide(gpu, order, prime, divisorData) != UInt128.One)
{
return false;
}
@@ -277,7 +279,7 @@ internal static partial class PrimeOrderCalculator
}
reduced /= primeFactor;
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One)
{
return false;
}
@@ -288,6 +290,7 @@ internal static partial class PrimeOrderCalculator
}
private static bool TryHeuristicFinishWide(
PrimeOrderCalculatorAccelerator gpu,
in UInt128 prime,
in UInt128 order,
in UInt128? previousOrder,
@@ -347,12 +350,12 @@ internal static partial class PrimeOrderCalculator
UInt128 candidate = candidates[i];
powUsed++;
if (Pow2ModWide(candidate, prime, divisorData) != UInt128.One)
if (Pow2ModWide(gpu, candidate, prime, divisorData) != UInt128.One)
{
continue;
}
if (!TryConfirmCandidateWide(prime, candidate, config, ref powUsed, powBudget, divisorData))
if (!TryConfirmCandidateWide(gpu, prime, candidate, config, ref powUsed, powBudget, divisorData))
{
continue;
}
@@ -536,7 +539,7 @@ internal static partial class PrimeOrderCalculator
}
}
private static bool TryConfirmCandidateWide(in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData)
private static bool TryConfirmCandidateWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData)
{
PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
if (factorization.Factors is null)
@@ -579,7 +582,7 @@ internal static partial class PrimeOrderCalculator
}
powUsed++;
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One)
{
return false;
}
@@ -855,7 +858,7 @@ internal static partial class PrimeOrderCalculator
return 64 + BitOperations.TrailingZeroCount(high);
}
private static UInt128 CalculateByFactorizationWide(in UInt128 prime, in MontgomeryDivisorData divisorData)
private static UInt128 CalculateByFactorizationWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in MontgomeryDivisorData? divisorData)
{
UInt128 phi = prime - UInt128.One;
Dictionary<UInt128, int> counts = new(capacity: 8);
@@ -879,7 +882,7 @@ internal static partial class PrimeOrderCalculator
if ((order % primeFactor) != UInt128.Zero)
{
UInt128 candidate = order / primeFactor;
if (Pow2ModWide(candidate, prime, divisorData) == UInt128.One)
if (Pow2ModWide(gpu, candidate, prime, divisorData) == UInt128.One)
{
order = candidate;
}
@@ -1026,7 +1029,7 @@ internal static partial class PrimeOrderCalculator
return true;
}
private static UInt128 Pow2ModWide(in UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData)
private static UInt128 Pow2ModWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData? divisorData)
{
if (modulus == UInt128.One)
{
@@ -1046,7 +1049,7 @@ internal static partial class PrimeOrderCalculator
}
}
GpuPow2ModStatus wideStatus = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, modulus, out UInt128 wideRemainder);
GpuPow2ModStatus wideStatus = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, modulus, out UInt128 wideRemainder);
if (wideStatus == GpuPow2ModStatus.Success)
{
return wideRemainder;
+6 -3
View File
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core;
@@ -48,6 +49,7 @@ internal static partial class PrimeOrderCalculator
// TODO: Remove branching on the CPU / GPU paths
// TODO: Split big non-static functions into smaller / extract static code to limit JIT time
public static ulong Calculate(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
ulong? previousOrder,
in MontgomeryDivisorData divisorData,
@@ -55,12 +57,13 @@ internal static partial class PrimeOrderCalculator
PrimeOrderHeuristicDevice device)
{
var scope = UsePow2Mode(device);
ulong order = CalculateInternal(prime, previousOrder, divisorData, config);
ulong order = CalculateInternal(gpu, prime, previousOrder, divisorData, config);
scope.Dispose();
return order;
}
public static UInt128 Calculate(
PrimeOrderCalculatorAccelerator gpu,
in UInt128 prime,
in UInt128? previousOrder,
in PrimeOrderSearchConfig config,
@@ -88,14 +91,14 @@ internal static partial class PrimeOrderCalculator
ulong prime64 = (ulong)prime;
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
divisorData = divisorPool.FromModulus(prime64);
ulong order64 = Calculate(prime64, previous, divisorData, config, device);
ulong order64 = Calculate(gpu, prime64, previous, divisorData, config, device);
divisorPool.Return(divisorData);
result = order64 == 0UL ? UInt128.Zero : (UInt128)order64;
}
else
{
divisorData = MontgomeryDivisorData.Empty;
result = CalculateWideInternal(prime, previousOrder, divisorData, config);
result = CalculateWideInternal(gpu, prime, previousOrder, divisorData, config);
}
scope.Dispose();
+34 -38
View File
@@ -87,45 +87,45 @@ public sealed class PrimeTester
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPrimeGpu(ulong n)
{
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static bool IsPrimeGpu(ulong n)
// {
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
int acceleratorIndex = gpu.AcceleratorIndex;
var inputView = gpu.Input.View;
var outputView = gpu.OutputByte.View;
// var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
// int acceleratorIndex = gpu.AcceleratorIndex;
// var inputView = gpu.Input.View;
// var outputView = gpu.OutputByte.View;
// GpuPrimeWorkLimiter.Acquire();
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
inputView.CopyFromCPU(stream, ref n, 1);
// // GpuPrimeWorkLimiter.Acquire();
// AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
// inputView.CopyFromCPU(stream, ref n, 1);
var kernelLauncher = gpu.SmallPrimeSieveKernelLauncher;
// var kernelLauncher = gpu.SmallPrimeSieveKernelLauncher;
kernelLauncher(
stream,
1,
inputView,
gpu.DevicePrimesLastOne,
gpu.DevicePrimesLastSeven,
gpu.DevicePrimesLastThree,
gpu.DevicePrimesLastNine,
gpu.DevicePrimesPow2LastOne,
gpu.DevicePrimesPow2LastSeven,
gpu.DevicePrimesPow2LastThree,
gpu.DevicePrimesPow2LastNine,
outputView);
// kernelLauncher(
// stream,
// 1,
// inputView,
// gpu.DevicePrimesLastOne,
// gpu.DevicePrimesLastSeven,
// gpu.DevicePrimesLastThree,
// gpu.DevicePrimesLastNine,
// gpu.DevicePrimesPow2LastOne,
// gpu.DevicePrimesPow2LastSeven,
// gpu.DevicePrimesPow2LastThree,
// gpu.DevicePrimesPow2LastNine,
// outputView);
byte flag = 0;
outputView.CopyToCPU(stream, ref flag, 1);
stream.Synchronize();
// byte flag = 0;
// outputView.CopyToCPU(stream, ref flag, 1);
// stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
PrimeOrderCalculatorAccelerator.Return(gpu);
// GpuPrimeWorkLimiter.Release();
// AcceleratorStreamPool.Return(acceleratorIndex, stream);
// PrimeOrderCalculatorAccelerator.Return(gpu);
// // GpuPrimeWorkLimiter.Release();
return flag != 0;
}
// return flag != 0;
// }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n)
@@ -189,10 +189,9 @@ public sealed class PrimeTester
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsPrimeBatchGpu(ReadOnlySpan<ulong> values, Span<byte> results)
public static void IsPrimeBatchGpu(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<ulong> values, Span<byte> results)
{
// GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeOrderCalculatorAccelerator.Rent(GpuBatchSize);
int acceleratorIndex = gpu.AcceleratorIndex;
int totalLength = values.Length;
int batchSize = GpuBatchSize;
@@ -236,7 +235,6 @@ public sealed class PrimeTester
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
PrimeOrderCalculatorAccelerator.Return(gpu);
// GpuPrimeWorkLimiter.Release();
}
@@ -265,7 +263,7 @@ public sealed class PrimeTester
return n.BinaryGcd(m) != 1UL;
}
internal static void SharesFactorWithMaxExponentBatch(ReadOnlySpan<ulong> values, Span<byte> results)
internal static void SharesFactorWithMaxExponentBatch(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<ulong> values, Span<byte> results)
{
// TODO: Route this batch helper through the shared GPU kernel pool from
// GpuUInt128BinaryGcdBenchmarks so we reuse cached kernels, pinned host buffers,
@@ -275,7 +273,6 @@ public sealed class PrimeTester
int length = values.Length;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
int acceleratorIndex = gpu.AcceleratorIndex;
gpu.EnsureCapacity(0, length);
MemoryBuffer1D<ulong, Stride1D.Dense>? inputBuffer = gpu.Input;
@@ -291,7 +288,6 @@ public sealed class PrimeTester
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
PrimeOrderCalculatorAccelerator.Return(gpu);
}
}
File diff suppressed because it is too large. Load diff
+6 -10
View File
@@ -44,27 +44,23 @@ public static partial class ULongExtensions
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Pow2MontgomeryModWindowedKeepGpu(this ulong exponent, in MontgomeryDivisorData divisor)
=> Pow2MontgomeryGpuCalculator.CalculateKeep(exponent, divisor);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Pow2MontgomeryModWindowedConvertGpu(this ulong exponent, in MontgomeryDivisorData divisor)
=> Pow2MontgomeryGpuCalculator.CalculateConvert(exponent, divisor);
public static ulong Pow2MontgomeryModWindowedKeepGpu(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, in MontgomeryDivisorData divisor)
=> Pow2MontgomeryGpuCalculator.CalculateKeep(gpu, exponent, divisor);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Pow2MontgomeryModWindowedConvertGpu(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, in MontgomeryDivisorData divisor)
=> Pow2MontgomeryGpuCalculator.CalculateConvert(gpu, exponent, divisor);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, ulong cycleLength, in MontgomeryDivisorData divisor)
{
ulong rotationCount = exponent % cycleLength;
return Pow2MontgomeryGpuCalculator.CalculateConvert(rotationCount, divisor);
return Pow2MontgomeryGpuCalculator.CalculateConvert(gpu, rotationCount, divisor);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Pow2MontgomeryModFromCycleRemainderGpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
public static ulong Pow2MontgomeryModFromCycleRemainderGpu(this ulong reducedExponent, PrimeOrderCalculatorAccelerator gpu, in MontgomeryDivisorData divisor)
{
return Pow2MontgomeryGpuCalculator.CalculateConvert(reducedExponent, divisor);
return Pow2MontgomeryGpuCalculator.CalculateConvert(gpu, reducedExponent, divisor);
}
}
+8 -7
View File
@@ -1,5 +1,6 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core;
@@ -98,7 +99,7 @@ public static partial class ULongExtensions
}
}
public static ulong CalculateOrder(this ulong q)
public static ulong CalculateOrder(this ulong q, PrimeOrderCalculatorAccelerator gpu)
{
if (q <= 2UL)
{
@@ -111,7 +112,7 @@ public static partial class ULongExtensions
int i = 0, primesLength = smallPrimes.Length;
UInt128 q128 = q,
cycle = MersenneDivisorCycles.GetCycle(q128);
cycle = MersenneDivisorCycles.GetCycle(gpu, q128);
// TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
// GPU computation (respecting the configured device) without promoting the result into
// the cache so the order calculator still benefits from cycle stepping while keeping the
@@ -860,7 +861,7 @@ public static partial class ULongExtensions
/// Computes 2^exponent mod modulus using iterative CRT composition from mod 10 up to modulus.
/// Only for modulus >= 10 and reasonable size.
/// </summary>
public static UInt128 PowModCrt(this ulong exponent, UInt128 modulus, MersenneDivisorCycles cycles)
public static UInt128 PowModCrt(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, UInt128 modulus, MersenneDivisorCycles cycles)
{
if (modulus < 10)
return PowMod(exponent, modulus); // fallback to classic
@@ -875,7 +876,7 @@ public static partial class ULongExtensions
for (; modulusCandidate <= modulus; modulusCandidate++)
{
cycle = MersenneDivisorCycles.GetCycle(modulusCandidate);
cycle = MersenneDivisorCycles.GetCycle(gpu, modulusCandidate);
remainderForCandidate = cycle > zero
? PowModWithCycle(exponent, modulusCandidate, cycle)
: PowMod(exponent, modulusCandidate);
@@ -935,7 +936,7 @@ public static partial class ULongExtensions
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool SharesFactorWithExponentMinusOne(this ulong exponent)
public static bool SharesFactorWithExponentMinusOne(this ulong exponent, PrimeOrderCalculatorAccelerator gpu)
{
ulong prime, value = exponent - 1UL;
value >>= BitOperations.TrailingZeroCount(value);
@@ -951,7 +952,7 @@ public static partial class ULongExtensions
continue;
}
if (exponent % prime.CalculateOrder() == 0UL)
if (exponent % prime.CalculateOrder(gpu) == 0UL)
{
return true;
}
@@ -963,7 +964,7 @@ public static partial class ULongExtensions
while (value % prime == 0UL);
}
if (value > 1UL && exponent % value.CalculateOrder() == 0UL)
if (value > 1UL && exponent % value.CalculateOrder(gpu) == 0UL)
{
return true;
}