BigInteger-friendly steppers added. MersenneResidueStepper converted to struct. Manual residue tracking replaced with steppers. Modulo operations replaced with steppers.

This commit is contained in:
mlemanczyk committed 2025-12-07 19:16:45 +01:00
1 parent fbf074ade0
commit 6887111128
9 files changed
+238 -421

No files matched your search

@@ -0,0 +1,38 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
/// <summary>
/// Maintains rolling remainders of an increasing sequence modulo a BigInteger cycle length.
/// Mirrors <see cref="CycleRemainderStepper"/> for values that exceed 64 bits.
/// </summary>
[method: MethodImpl(MethodImplOptions.AggressiveInlining)]
public struct BigIntegerCycleRemainderStepper(BigInteger cycleLength)
{
private readonly BigInteger _cycleLength = cycleLength;
private BigInteger _previousValue = BigInteger.Zero;
private BigInteger _currentRemainder = BigInteger.Zero;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BigInteger Initialize(BigInteger value)
{
_previousValue = value;
_currentRemainder = value % _cycleLength;
return _currentRemainder;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BigInteger ComputeNext(BigInteger value)
{
BigInteger delta = value - _previousValue;
_previousValue = value;
_currentRemainder += delta;
if (_currentRemainder >= _cycleLength)
{
_currentRemainder %= _cycleLength;
}
return _currentRemainder;
}
}
@@ -0,0 +1,60 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
/// <summary>
/// Tracks successive residues of <c>baseValue^exponent mod modulus</c> for ascending exponents using <see cref="BigInteger"/> inputs.
/// This avoids recomputing full modular exponentiation when stepping forward by a known delta.
/// </summary>
public struct BigIntegerExponentStepper(BigInteger modulus, BigInteger baseValue)
{
private readonly BigInteger _modulus = modulus;
private readonly BigInteger _baseValue = baseValue % modulus;
private BigInteger PreviousExponent = BigInteger.Zero;
private BigInteger CurrentResidue = BigInteger.One % modulus;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BigInteger Initialize(BigInteger exponent)
{
CurrentResidue = BigInteger.ModPow(_baseValue, exponent, _modulus);
PreviousExponent = exponent;
return CurrentResidue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool InitializeIsUnity(BigInteger exponent)
{
Initialize(exponent);
return CurrentResidue.IsOne;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BigInteger ComputeNext(BigInteger exponent)
{
if (exponent == PreviousExponent)
{
return CurrentResidue;
}
BigInteger delta = exponent - PreviousExponent;
BigInteger deltaResidue = BigInteger.ModPow(_baseValue, delta, _modulus);
CurrentResidue = (CurrentResidue * deltaResidue) % _modulus;
PreviousExponent = exponent;
return CurrentResidue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ComputeNextIsUnity(BigInteger exponent)
{
ComputeNext(exponent);
return CurrentResidue.IsOne;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
PreviousExponent = BigInteger.Zero;
CurrentResidue = BigInteger.One % _modulus;
}
}
@@ -2,6 +2,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Globalization;
using System.IO;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
@@ -294,40 +295,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
ulong limitHigh = limit.High;
ulong divisorLow = divisor.Low;
// Keep the divisibility filters aligned with the divisor-cycle generator so the
// CPU path never requests cycles that were skipped during cache creation.
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
byte step10;
byte step8;
byte step3;
byte step7;
byte step11;
byte step13;
byte step17;
byte step19;
byte remainder10;
byte remainder8;
byte remainder3;
byte remainder7;
byte remainder11;
byte remainder13;
byte remainder17;
byte remainder19;
if (stepHigh == 0UL && limitHigh == 0UL)
{
step10 = (byte)(stepLow % 10UL);
step8 = (byte)(stepLow & 7UL);
step3 = (byte)(stepLow % 3UL);
step7 = (byte)(stepLow % 7UL);
step11 = (byte)(stepLow % 11UL);
step13 = (byte)(stepLow % 13UL);
step17 = (byte)(stepLow % 17UL);
step19 = (byte)(stepLow % 19UL);
ulong maxK = allowedMax > 0UL ? (allowedMax - 1UL) / stepLow : 0UL;
if (maxK == 0UL)
{
@@ -350,15 +319,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
allowedMax,
startK,
maxK,
decimalMask,
step10,
step8,
step3,
step7,
step11,
step13,
step17,
step19,
ref currentK,
out processedTop,
out foundDivisor);
@@ -381,15 +341,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
allowedMax,
1UL,
lowerEnd,
decimalMask,
step10,
step8,
step3,
step7,
step11,
step13,
step17,
step19,
ref currentK,
out processedBottom,
out foundDivisor);
@@ -400,29 +351,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
}
ulong divisorHigh = divisor.High;
step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL);
step8 = (byte)(stepLow % 8UL);
step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL);
step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL);
step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL);
step13 = (byte)((((stepHigh % 13UL) * 3UL) + (stepLow % 13UL)) % 13UL);
step17 = (byte)(((stepHigh % 17UL) + (stepLow % 17UL)) % 17UL);
step19 = (byte)((((stepHigh % 19UL) * 17UL) + (stepLow % 19UL)) % 19UL);
remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL);
remainder8 = (byte)(divisorLow % 8UL);
remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL);
remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL);
remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL);
remainder13 = (byte)((((divisorHigh % 13UL) * 3UL) + (divisorLow % 13UL)) % 13UL);
remainder17 = (byte)(((divisorHigh % 17UL) + (divisorLow % 17UL)) % 17UL);
remainder19 = (byte)((((divisorHigh % 19UL) * 17UL) + (divisorLow % 19UL)) % 19UL);
var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor);
var divisorPool = MontgomeryDivisorDataPool.Shared;
while (divisor.CompareTo(limit) <= 0)
{
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
if (residueStepper.IsAdmissible())
{
ulong candidate = divisor.Low;
MontgomeryDivisorData divisorData = divisorPool.FromModulus(candidate);
@@ -468,14 +402,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
divisor.Add(step);
currentK++;
remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8);
remainder3 = AddMod3(remainder3, step3);
remainder7 = AddMod7(remainder7, step7);
remainder11 = AddMod11(remainder11, step11);
remainder13 = AddMod13(remainder13, step13);
remainder17 = AddMod17(remainder17, step17);
remainder19 = AddMod19(remainder19, step19);
residueStepper.Advance();
}
processedAll = true;
@@ -510,27 +437,29 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
byte step10 = (byte)(step % 10);
byte step8 = (byte)(step % 8);
byte step3 = (byte)(step % 3);
byte step7 = (byte)(step % 7);
byte step11 = (byte)(step % 11);
byte step13 = (byte)(step % 13);
byte step17 = (byte)(step % 17);
byte step19 = (byte)(step % 19);
var rem10 = new BigIntegerCycleRemainderStepper(10);
var rem8 = new BigIntegerCycleRemainderStepper(8);
var rem5 = new BigIntegerCycleRemainderStepper(5);
var rem3 = new BigIntegerCycleRemainderStepper(3);
var rem7 = new BigIntegerCycleRemainderStepper(7);
var rem11 = new BigIntegerCycleRemainderStepper(11);
var rem13 = new BigIntegerCycleRemainderStepper(13);
var rem17 = new BigIntegerCycleRemainderStepper(17);
var rem19 = new BigIntegerCycleRemainderStepper(19);
byte remainder10 = (byte)(divisor % 10);
byte remainder8 = (byte)(divisor % 8);
byte remainder3 = (byte)(divisor % 3);
byte remainder7 = (byte)(divisor % 7);
byte remainder11 = (byte)(divisor % 11);
byte remainder13 = (byte)(divisor % 13);
byte remainder17 = (byte)(divisor % 17);
byte remainder19 = (byte)(divisor % 19);
byte remainder10 = (byte)rem10.Initialize(divisor);
byte remainder8 = (byte)rem8.Initialize(divisor);
byte remainder5 = (byte)rem5.Initialize(divisor);
byte remainder3 = (byte)rem3.Initialize(divisor);
byte remainder7 = (byte)rem7.Initialize(divisor);
byte remainder11 = (byte)rem11.Initialize(divisor);
byte remainder13 = (byte)rem13.Initialize(divisor);
byte remainder17 = (byte)rem17.Initialize(divisor);
byte remainder19 = (byte)rem19.Initialize(divisor);
while (divisor <= allowedMax)
{
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
{
if (IsProbablePrimeBigInteger(divisor))
@@ -552,14 +481,15 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
currentK += BigInteger.One;
divisor += step;
remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8);
remainder3 = AddMod3(remainder3, step3);
remainder7 = AddMod7(remainder7, step7);
remainder11 = AddMod11(remainder11, step11);
remainder13 = AddMod13(remainder13, step13);
remainder17 = AddMod17(remainder17, step17);
remainder19 = AddMod19(remainder19, step19);
remainder10 = (byte)rem10.ComputeNext(divisor);
remainder8 = (byte)rem8.ComputeNext(divisor);
remainder5 = (byte)rem5.ComputeNext(divisor);
remainder3 = (byte)rem3.ComputeNext(divisor);
remainder7 = (byte)rem7.ComputeNext(divisor);
remainder11 = (byte)rem11.ComputeNext(divisor);
remainder13 = (byte)rem13.ComputeNext(divisor);
remainder17 = (byte)rem17.ComputeNext(divisor);
remainder19 = (byte)rem19.ComputeNext(divisor);
if (divisor > allowedMax)
{
@@ -579,15 +509,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
ulong allowedMax,
ulong startK,
ulong endK,
ushort decimalMask,
byte step10,
byte step8,
byte step3,
byte step7,
byte step11,
byte step13,
byte step17,
byte step19,
ref ulong currentK,
out bool processedAll,
out ulong foundDivisor)
@@ -619,14 +540,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
ulong startDivisor = (ulong)startDivisor128;
ulong rangeLimit = (ulong)rangeLimit128;
byte remainder10 = (byte)(startDivisor % 10UL);
byte remainder8 = (byte)(startDivisor & 7UL);
byte remainder3 = (byte)(startDivisor % 3UL);
byte remainder7 = (byte)(startDivisor % 7UL);
byte remainder11 = (byte)(startDivisor % 11UL);
byte remainder13 = (byte)(startDivisor % 13UL);
byte remainder17 = (byte)(startDivisor % 17UL);
byte remainder19 = (byte)(startDivisor % 19UL);
var residueStepper = new MersenneDivisorResidueStepper(prime, (GpuUInt128)step128, (GpuUInt128)startDivisor128);
return CheckDivisors64(
gpu,
@@ -634,23 +548,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
step,
rangeLimit,
startDivisor,
decimalMask,
step10,
step8,
step3,
step7,
step11,
step13,
step17,
step19,
remainder10,
remainder8,
remainder3,
remainder7,
remainder11,
remainder13,
remainder17,
remainder19,
ref residueStepper,
ref currentK,
out processedAll,
out foundDivisor);
@@ -662,23 +560,7 @@ private bool CheckDivisors64(
ulong step,
ulong limit,
ulong divisor,
ushort decimalMask,
byte step10,
byte step8,
byte step3,
byte step7,
byte step11,
byte step13,
byte step17,
byte step19,
byte remainder10,
byte remainder8,
byte remainder3,
byte remainder7,
byte remainder11,
byte remainder13,
byte remainder17,
byte remainder19,
ref MersenneDivisorResidueStepper residueStepper,
ref ulong currentK,
out bool processedAll,
out ulong foundDivisor)
@@ -687,44 +569,41 @@ private bool CheckDivisors64(
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
if (!canAdvance)
{
if (divisor <= limit)
if (divisor <= limit && residueStepper.IsAdmissible())
{
if (remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0 && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
ulong divisorCycle;
if (!_tryCalculateCycleLengthForExponent(
divisor,
gpu,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed) || computedCycle == 0UL)
{
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
ulong divisorCycle;
if (!_tryCalculateCycleLengthForExponent(
divisor,
gpu,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = _calculateCycleLength(
divisor,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
else
{
divisorCycle = computedCycle;
}
divisorCycle = _calculateCycleLength(
divisor,
gpu,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
else
{
divisorCycle = computedCycle;
}
divisorPool.Return(divisorData);
divisorPool.Return(divisorData);
if (divisorCycle == prime)
{
foundDivisor = divisor;
processedAll = true;
return true;
}
if (divisorCycle == prime)
{
foundDivisor = divisor;
processedAll = true;
return true;
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
}
@@ -743,8 +622,7 @@ private bool CheckDivisors64(
ulong remainingIterations = ((limit - divisor) / step) + 1UL;
while (true)
{
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
if (residueStepper.IsAdmissible())
{
MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor);
ulong divisorCycle;
@@ -792,14 +670,7 @@ private bool CheckDivisors64(
divisor += step;
currentK++;
remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8);
remainder3 = AddMod3(remainder3, step3);
remainder7 = AddMod7(remainder7, step7);
remainder11 = AddMod11(remainder11, step11);
remainder13 = AddMod13(remainder13, step13);
remainder17 = AddMod17(remainder17, step17);
remainder19 = AddMod19(remainder19, step19);
residueStepper.Advance();
}
}
@@ -870,7 +741,8 @@ private bool CheckDivisors64(
continue;
}
BigInteger x = BigInteger.ModPow(baseValue, d, value);
var expStepper = new BigIntegerExponentStepper(value, baseValue);
BigInteger x = expStepper.Initialize(d);
if (x == 1 || x == value - 1)
{
continue;
@@ -879,7 +751,8 @@ private bool CheckDivisors64(
bool witnessFound = true;
for (int r = 1; r < s; r++)
{
x = BigInteger.ModPow(x, 2, value);
BigInteger targetExponent = d << r;
x = expStepper.ComputeNext(targetExponent);
if (x == value - 1)
{
witnessFound = false;
+7 -42
View File
@@ -2,37 +2,20 @@ using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
public struct CycleRemainderStepper
public struct CycleRemainderStepper(ulong cycleLength)
{
private readonly ulong _cycleLength;
private ulong _previousPrime;
private ulong _currentRemainder;
private bool _hasState;
public CycleRemainderStepper(ulong cycleLength)
{
if (cycleLength == 0UL)
{
throw new ArgumentOutOfRangeException(nameof(cycleLength), "Cycle length must be non-zero.");
}
_cycleLength = cycleLength;
_previousPrime = 0UL;
_currentRemainder = 0UL;
_hasState = false;
}
private readonly ulong _cycleLength = cycleLength;
private ulong _previousPrime = 0UL;
private ulong _currentRemainder = 0UL;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
// TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
// measured fastest in MersenneDivisorCycleLengthGpuBenchmarks, avoiding the extra
// method call when scanners need to restart stepping.
_previousPrime = 0UL;
_currentRemainder = 0UL;
_hasState = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong Initialize(ulong prime)
{
ulong cycleLength = _cycleLength;
@@ -44,24 +27,15 @@ public struct CycleRemainderStepper
// MersenneDivisorCycleLengthGpuBenchmarks winner for both CPU and GPU call sites.
remainder %= cycleLength;
}
_previousPrime = prime;
_currentRemainder = remainder;
_hasState = true;
return remainder;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ComputeNext(ulong prime)
{
if (!_hasState)
{
throw new InvalidOperationException("CycleRemainderStepper must be initialized before computing.");
}
if (prime <= _previousPrime)
{
throw new ArgumentOutOfRangeException(nameof(prime), "Primes must be processed in strictly increasing order.");
}
ulong cycleLength = _cycleLength;
ulong delta = prime - _previousPrime;
_previousPrime = prime;
@@ -90,13 +64,4 @@ public struct CycleRemainderStepper
}
return _currentRemainder;
}
public void CaptureState(out ulong previousPrime, out ulong previousRemainder, out bool hasState)
{
// TODO: Expose these fields directly once the residue scanners adopt the single-cycle helper
// so the hot path can read them without paying for an additional wrapper call per iteration.
previousPrime = _previousPrime;
previousRemainder = _currentRemainder;
hasState = _hasState;
}
}
@@ -1,28 +1,21 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
/// <summary>
/// Tracks successive Montgomery residues for a single divisor while scanning exponent candidates on the CPU path.
/// Callers are responsible for seeding the stepper once per divisor and then advancing exponents in ascending order.
/// </summary>
internal struct ExponentRemainderStepperCpu
internal struct ExponentRemainderStepperCpu(in MontgomeryDivisorData divisor)
{
private readonly MontgomeryDivisorData _divisor;
private readonly ulong _modulus;
private readonly ulong _nPrime;
private readonly ulong _montgomeryOne;
public ulong PreviousExponent;
private ulong _currentMontgomery;
public ExponentRemainderStepperCpu(in MontgomeryDivisorData divisor)
{
_divisor = divisor;
_modulus = divisor.Modulus;
_nPrime = divisor.NPrime;
_montgomeryOne = divisor.MontgomeryOne;
PreviousExponent = 0UL;
_currentMontgomery = divisor.MontgomeryOne;
}
private readonly MontgomeryDivisorData _divisor = divisor;
private readonly ulong _modulus = divisor.Modulus;
private readonly ulong _nPrime = divisor.NPrime;
private readonly ulong _montgomeryOne = divisor.MontgomeryOne;
private ulong PreviousExponent = 0UL;
private ulong _currentMontgomery = divisor.MontgomeryOne;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool MatchesDivisor(in MontgomeryDivisorData divisor)
=> _modulus == divisor.Modulus && _nPrime == divisor.NPrime && _montgomeryOne == divisor.MontgomeryOne;
@@ -30,6 +23,7 @@ internal struct ExponentRemainderStepperCpu
/// Clears the cached Montgomery residue so the stepper can be reused for another divisor.
/// Callers must reinitialize the stepper before consuming any residues after a reset.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
PreviousExponent = 0UL;
@@ -42,6 +36,7 @@ internal struct ExponentRemainderStepperCpu
/// </summary>
/// <param name="exponent">First exponent evaluated for the current divisor. The caller must reuse the same divisor metadata.</param>
/// <returns>The canonical residue produced by <c>2^exponent mod divisor</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong InitializeCpu(ulong exponent)
{
InitializeCpuState(exponent);
@@ -53,6 +48,7 @@ internal struct ExponentRemainderStepperCpu
/// </summary>
/// <param name="exponent">First exponent evaluated for the current divisor. Must match the divisor passed to the constructor.</param>
/// <returns><see langword="true"/> when the seed exponent produces a Montgomery residue equal to one.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool InitializeCpuIsUnity(ulong exponent)
{
InitializeCpuState(exponent);
@@ -65,6 +61,7 @@ internal struct ExponentRemainderStepperCpu
/// </summary>
/// <param name="exponent">Next exponent in the ascending sequence. Callers must never move backwards or reuse an older exponent.</param>
/// <returns>The canonical residue produced by <c>2^exponent mod divisor</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ComputeNextCpu(ulong exponent)
{
ulong delta = exponent - PreviousExponent;
@@ -84,6 +81,7 @@ internal struct ExponentRemainderStepperCpu
/// </summary>
/// <param name="exponent">Next exponent in the ascending sequence.</param>
/// <returns><see langword="true"/> when the canonical residue equals one.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ComputeNextIsUnity(ulong exponent)
{
ulong delta = exponent - PreviousExponent;
@@ -103,6 +101,7 @@ internal struct ExponentRemainderStepperCpu
/// <param name="montgomeryResult">Residue in Montgomery form produced externally for the configured divisor.</param>
/// <param name="isUnity">Outputs whether the supplied residue equals Montgomery one.</param>
/// <returns><see langword="true"/> when the residue is accepted and cached.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryInitializeFromMontgomeryResult(ulong exponent, ulong montgomeryResult, out bool isUnity)
{
_currentMontgomery = montgomeryResult;
@@ -119,6 +118,7 @@ internal struct ExponentRemainderStepperCpu
/// <param name="montgomeryDelta">Montgomery residue delta corresponding to the exponent gap.</param>
/// <param name="isUnity">Outputs whether the resulting residue equals Montgomery one.</param>
/// <returns><see langword="true"/> when the advance succeeds.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryAdvanceWithMontgomeryDelta(ulong exponent, ulong montgomeryDelta, out bool isUnity)
{
// TODO: Once the divisor-cycle cache exposes a direct Montgomery delta, multiply it here instead
@@ -130,11 +130,13 @@ internal struct ExponentRemainderStepperCpu
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void InitializeCpuState(ulong exponent)
{
_currentMontgomery = exponent.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor);
PreviousExponent = exponent;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly ulong ReduceCurrent() => _currentMontgomery.MontgomeryMultiplyCpu(1UL, _modulus, _nPrime);
}
@@ -11,7 +11,7 @@ internal struct ExponentRemainderStepperGpu
{
private readonly ulong _modulus;
private readonly ReadOnlyGpuUInt128 _modulusWide;
public ulong PreviousExponent;
private ulong PreviousExponent;
private GpuUInt128 _currentResidue;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu;
@@ -253,16 +254,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
ulong startK = minK < 1UL ? 1UL : minK;
ulong stepValue = (ulong)twoP128;
byte step10 = (byte)(stepValue % 10UL);
byte step8 = (byte)(stepValue & 7UL);
byte step3 = (byte)(stepValue % 3UL);
byte step7 = (byte)(stepValue % 7UL);
byte step11 = (byte)(stepValue % 11UL);
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
Span<ulong> divisorStorage = divisors.AsSpan();
Span<ulong> exponentStorage = exponents.AsSpan();
Span<GpuDivisorPartialData> divisorDataStorage = divisorData.AsSpan();
@@ -289,12 +280,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
ulong prime,
UInt128 twoP128,
UInt128 allowedMax128,
byte step10,
byte step8,
byte step3,
byte step7,
byte step11,
ushort decimalMask,
ArrayView1D<GpuDivisorPartialData, Stride1D.Dense> divisorDataView,
ArrayView1D<int, Stride1D.Dense> offsetView,
ArrayView1D<int, Stride1D.Dense> countView,
@@ -329,6 +314,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
return true;
}
var residueStepper = new MersenneDivisorResidueStepper(prime, (GpuUInt128)twoP128, (GpuUInt128)startDivisor128);
UInt128 rangeLimit128 = (twoP128 * rangeEndK) + UInt128.One;
if (rangeLimit128 > allowedMax128)
{
@@ -338,12 +324,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
UInt128 rangeCount128 = ((rangeLimit128 - startDivisor128) / twoP128) + UInt128.One;
ulong rangeRemaining = rangeCount128 > ulong.MaxValue ? ulong.MaxValue : (ulong)rangeCount128;
UInt128 currentRangeDivisor128 = startDivisor128;
ulong currentRangeDivisorValue = (ulong)currentRangeDivisor128;
byte rangeRemainder10 = (byte)(currentRangeDivisorValue % 10UL);
byte rangeRemainder8 = (byte)(currentRangeDivisorValue & 7UL);
byte rangeRemainder3 = (byte)(currentRangeDivisorValue % 3UL);
byte rangeRemainder7 = (byte)(currentRangeDivisorValue % 7UL);
byte rangeRemainder11 = (byte)(currentRangeDivisorValue % 11UL);
while (rangeRemaining > 0UL && !composite)
{
@@ -356,16 +336,11 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
UInt128 nextDivisor128 = currentRangeDivisor128;
int admissibleCount = 0;
byte localRemainder10 = rangeRemainder10;
byte localRemainder8 = rangeRemainder8;
byte localRemainder3 = rangeRemainder3;
byte localRemainder7 = rangeRemainder7;
byte localRemainder11 = rangeRemainder11;
var localStepper = residueStepper;
for (int i = 0; i < chunkCount; i++)
{
bool passesSmallModuli = localRemainder3 != 0 && localRemainder7 != 0 && localRemainder11 != 0;
if (passesSmallModuli && (localRemainder8 == 1 || localRemainder8 == 7) && ((decimalMask >> localRemainder10) & 1) != 0)
if (localStepper.IsAdmissible())
{
ulong divisorValue = (ulong)nextDivisor128;
MontgomeryDivisorData montgomeryData = divisorPool.FromModulus(divisorValue);
@@ -389,18 +364,10 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
}
nextDivisor128 += twoP128;
localRemainder10 = AddMod10(localRemainder10, step10);
localRemainder8 = AddMod8(localRemainder8, step8);
localRemainder3 = AddMod3(localRemainder3, step3);
localRemainder7 = AddMod7(localRemainder7, step7);
localRemainder11 = AddMod11(localRemainder11, step11);
localStepper.Advance();
}
rangeRemainder10 = localRemainder10;
rangeRemainder8 = localRemainder8;
rangeRemainder3 = localRemainder3;
rangeRemainder7 = localRemainder7;
rangeRemainder11 = localRemainder11;
residueStepper = localStepper;
processedCountLocal += (ulong)chunkCount;
@@ -456,12 +423,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
prime,
twoP128,
allowedMax128,
step10,
step8,
step3,
step7,
step11,
decimalMask,
divisorDataView,
offsetView,
countView,
@@ -502,12 +463,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
prime,
twoP128,
allowedMax128,
step10,
step8,
step3,
step7,
step11,
decimalMask,
divisorDataView,
offsetView,
countView,
@@ -550,69 +505,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
return computedCycle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod3(byte value, byte delta)
{
const int Modulus = 3;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod7(byte value, byte delta)
{
const int Modulus = 7;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod10(byte value, byte delta)
{
const int Modulus = 10;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod11(byte value, byte delta)
{
const int Modulus = 11;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod8(byte value, byte delta)
{
return (byte)((value + delta) & 7);
}
private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime)
{
@@ -3,52 +3,34 @@ using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core;
internal struct MersenneDivisorResidueStepper
[method: MethodImpl(MethodImplOptions.AggressiveInlining)]
internal struct MersenneDivisorResidueStepper(ulong prime, in GpuUInt128 step, in GpuUInt128 firstDivisor)
{
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
private readonly ushort decimalMask;
private readonly byte step10;
private readonly byte step8;
private readonly byte step5;
private readonly byte step3;
private readonly byte step7;
private readonly byte step11;
private readonly ushort decimalMask = (prime & 3UL) == 3UL ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
private readonly byte step10 = ComputeModulo(step, 10, multiplier: 6);
private readonly byte step8 = (byte)(step.Low % 8UL);
private readonly byte step5 = ComputeModulo(step, 5, multiplier: 1);
private readonly byte step3 = ComputeModulo(step, 3, multiplier: 1);
private readonly byte step7 = ComputeModulo(step, 7, multiplier: 2);
private readonly byte step11 = ComputeModulo(step, 11, multiplier: 5);
private readonly byte step13 = ComputeModulo(step, 13, multiplier: 3);
private readonly byte step17 = ComputeModulo(step, 17, multiplier: 1);
private readonly byte step19 = ComputeModulo(step, 19, multiplier: 17);
public byte Remainder10 { get; private set; }
private byte Remainder10 = ComputeModulo(firstDivisor, 10, multiplier: 6);
private byte Remainder8 = (byte)(firstDivisor.Low % 8UL);
private byte Remainder5 = ComputeModulo(firstDivisor, 5, multiplier: 1);
private byte Remainder3 = ComputeModulo(firstDivisor, 3, multiplier: 1);
private byte Remainder7 = ComputeModulo(firstDivisor, 7, multiplier: 2);
private byte Remainder11 = ComputeModulo(firstDivisor, 11, multiplier: 5);
private byte Remainder13 = ComputeModulo(firstDivisor, 13, multiplier: 3);
private byte Remainder17 = ComputeModulo(firstDivisor, 17, multiplier: 1);
private byte Remainder19 = ComputeModulo(firstDivisor, 19, multiplier: 17);
public byte Remainder8 { get; private set; }
public byte Remainder5 { get; private set; }
public byte Remainder3 { get; private set; }
public byte Remainder7 { get; private set; }
public byte Remainder11 { get; private set; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MersenneDivisorResidueStepper(ulong prime, in GpuUInt128 step, in GpuUInt128 firstDivisor)
{
decimalMask = (prime & 3UL) == 3UL ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
step10 = ComputeModulo(step, 10, multiplier: 6);
step8 = (byte)(step.Low % 8UL);
step5 = ComputeModulo(step, 5, multiplier: 1);
step3 = ComputeModulo(step, 3, multiplier: 1);
step7 = ComputeModulo(step, 7, multiplier: 2);
step11 = ComputeModulo(step, 11, multiplier: 5);
Remainder10 = ComputeModulo(firstDivisor, 10, multiplier: 6);
Remainder8 = (byte)(firstDivisor.Low % 8UL);
Remainder5 = ComputeModulo(firstDivisor, 5, multiplier: 1);
Remainder3 = ComputeModulo(firstDivisor, 3, multiplier: 1);
Remainder7 = ComputeModulo(firstDivisor, 7, multiplier: 2);
Remainder11 = ComputeModulo(firstDivisor, 11, multiplier: 5);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsAdmissible()
{
if (((decimalMask >> Remainder10) & 1) == 0)
@@ -61,7 +43,7 @@ internal struct MersenneDivisorResidueStepper
return false;
}
if (Remainder3 == 0 || Remainder5 == 0 || Remainder7 == 0 || Remainder11 == 0)
if (Remainder3 == 0 || Remainder5 == 0 || Remainder7 == 0 || Remainder11 == 0 || Remainder13 == 0 || Remainder17 == 0 || Remainder19 == 0)
{
return false;
}
@@ -78,6 +60,9 @@ internal struct MersenneDivisorResidueStepper
Remainder3 = AddMod(Remainder3, step3, 3);
Remainder7 = AddMod(Remainder7, step7, 7);
Remainder11 = AddMod(Remainder11, step11, 11);
Remainder13 = AddMod(Remainder13, step13, 13);
Remainder17 = AddMod(Remainder17, step17, 17);
Remainder19 = AddMod(Remainder19, step19, 19);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+10 -8
View File
@@ -1,19 +1,17 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
public class MersenneResidueStepper
public struct MersenneResidueStepper
{
private UInt128 m;
private readonly UInt128 m;
private ulong p;
private UInt128 pow2_mod;
private UInt128 M_mod;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MersenneResidueStepper(UInt128 modulus, ulong p0, UInt128 M_mod_m_at_p0)
{
if (modulus == 0)
{
throw new ArgumentException("Modulus must be positive.");
}
m = modulus;
p = p0;
@@ -32,8 +30,10 @@ public class MersenneResidueStepper
// TODO: Inline this accessor into callers so the hot residue path reads the backing fields directly
// instead of jumping through a wrapper method every iteration.
public (ulong, UInt128) State() => (p, M_mod);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly (ulong, UInt128) State() => (p, M_mod);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (ulong, UInt128) Step(long delta)
{
if (delta == 0)
@@ -67,6 +67,7 @@ public class MersenneResidueStepper
return State();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (ulong, UInt128) To(ulong p_target)
{
// TODO: Collapse this wrapper once callers can invoke Step directly; the extra indirection shows up
@@ -76,6 +77,7 @@ public class MersenneResidueStepper
return Step(delta);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static UInt128 ModInverse(UInt128 a, UInt128 m)
{
UInt128 m0 = m;