Modulo operators replaced with steppers, where possible.

This commit is contained in:
mlemanczyk committed 2025-12-08 20:57:47 +01:00
1 parent 475507ce0c
commit f7dbc017a9
15 files changed
+506 -126

No files matched your search

@@ -0,0 +1,62 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
public struct BidirectionalCycleRemainderStepper(ulong modulus)
{
private readonly ulong _modulus = modulus;
private ulong _previous = 0UL;
private ulong _remainder = 0UL;
public readonly ulong Modulus
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _modulus;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initialize(ulong value)
{
_remainder = value.ReduceCycleRemainder(_modulus);
_previous = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong Step(ulong value)
{
long delta = unchecked((long)value - (long)_previous);
_previous = value;
if (delta == 0)
{
return _remainder;
}
if (delta > 0)
{
ulong step = ((ulong)delta).ReduceCycleRemainder(_modulus);
ulong sum = _remainder + step;
if (sum >= _modulus)
{
sum -= _modulus;
}
_remainder = sum;
return _remainder;
}
ulong backward = ((ulong)-delta).ReduceCycleRemainder(_modulus);
if (backward > _remainder)
{
ulong diff = backward - _remainder;
ulong wrap = diff.ReduceCycleRemainder(_modulus);
_remainder = wrap == 0 ? 0UL : _modulus - wrap;
return _remainder;
}
_remainder -= backward;
return _remainder;
}
}
@@ -18,7 +18,7 @@ public struct BigIntegerCycleRemainderStepper(BigInteger cycleLength)
public BigInteger Initialize(BigInteger value) public BigInteger Initialize(BigInteger value)
{ {
_previousValue = value; _previousValue = value;
_currentRemainder = value % _cycleLength; _currentRemainder = value.ReduceCycleRemainder(_cycleLength);
return _currentRemainder; return _currentRemainder;
} }
@@ -27,11 +27,7 @@ public struct BigIntegerCycleRemainderStepper(BigInteger cycleLength)
{ {
BigInteger delta = value - _previousValue; BigInteger delta = value - _previousValue;
_previousValue = value; _previousValue = value;
_currentRemainder += delta; _currentRemainder = (_currentRemainder + delta).ReduceCycleRemainder(_cycleLength);
if (_currentRemainder >= _cycleLength)
{
_currentRemainder %= _cycleLength;
}
return _currentRemainder; return _currentRemainder;
} }
+113
View File
@@ -0,0 +1,113 @@
using System.Numerics;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
/// <summary>
/// BigInteger helpers including cycle-remainder reduction using the subtract ladder that benchmarked best.
/// Shared stats/warm set stay here so ulong reductions can reuse them through ULongExtensions.
/// </summary>
public static partial class BigIntegerExtensions
{
// private const int UnrolledSubtractCount = 6;
// private static readonly HashSet<ulong> WarmModuli;
// private static long _hits;
// private static long _misses;
// static BigIntegerExtensions()
// {
// ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
// WarmModuli = new HashSet<ulong>(snapshot.Length);
// for (int i = 0; i < snapshot.Length; i++)
// {
// ulong cycle = snapshot[i];
// if (cycle > 1UL)
// {
// WarmModuli.Add(cycle);
// }
// }
// }
// public static void DumpCycleRemainderStats()
// {
// long hits = Interlocked.Read(ref _hits);
// long misses = Interlocked.Read(ref _misses);
// Console.WriteLine($"CycleRemainderReducerCache: hits={hits}, misses={misses}");
// }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BigInteger ReduceCycleRemainder(this BigInteger value, BigInteger modulus)
{
// Track(modulus);
// DumpCycleRemainderStats();
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
return value % modulus;
}
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// internal static void Track(in BigInteger modulus)
// {
// if (modulus <= ulong.MaxValue)
// {
// Track((ulong)modulus);
// }
// else
// {
// Interlocked.Increment(ref _misses);
// }
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// internal static void Track(ulong modulus)
// {
// if (WarmModuli.Contains(modulus))
// {
// Interlocked.Increment(ref _hits);
// }
// else
// {
// Interlocked.Increment(ref _misses);
// }
// }
}
@@ -55,7 +55,7 @@ internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelera
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues. // Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors. // They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
var exponentStepper = new ExponentRemainderStepperCpu(divisorData); var exponentStepper = new ExponentRemainderStepperCpu(divisorData, divisorCycle);
var cycleStepper = new CycleRemainderStepper(divisorCycle); var cycleStepper = new CycleRemainderStepper(divisorCycle);
+3 -15
View File
@@ -19,14 +19,7 @@ public struct CycleRemainderStepper(ulong cycleLength)
public ulong Initialize(ulong prime) public ulong Initialize(ulong prime)
{ {
ulong cycleLength = _cycleLength; ulong cycleLength = _cycleLength;
ulong remainder = prime; ulong remainder = prime.ReduceCycleRemainder(cycleLength);
if (remainder >= cycleLength)
{
// TODO: Swap this `%` for the shared divisor-cycle remainder helper so initialization reuses the cached
// cycle deltas benchmarked faster than on-the-fly modulo work, matching the
// MersenneDivisorCycleLengthGpuBenchmarks winner for both CPU and GPU call sites.
remainder %= cycleLength;
}
_previousPrime = prime; _previousPrime = prime;
_currentRemainder = remainder; _currentRemainder = remainder;
@@ -43,9 +36,7 @@ public struct CycleRemainderStepper(ulong cycleLength)
if (ulong.MaxValue - _currentRemainder < delta) if (ulong.MaxValue - _currentRemainder < delta)
{ {
UInt128 extended = (UInt128)_currentRemainder + delta; UInt128 extended = (UInt128)_currentRemainder + delta;
// TODO: Replace this `%` with the UInt128-aware cycle reducer so large deltas use the cached subtraction _currentRemainder = ((ulong)(extended % cycleLength)).ReduceCycleRemainder(cycleLength);
// ladder instead of falling back to the slower modulo implementation.
_currentRemainder = (ulong)(extended % cycleLength);
return _currentRemainder; return _currentRemainder;
} }
@@ -56,10 +47,7 @@ public struct CycleRemainderStepper(ulong cycleLength)
_currentRemainder -= cycleLength; _currentRemainder -= cycleLength;
if (_currentRemainder >= cycleLength) if (_currentRemainder >= cycleLength)
{ {
// TODO: Route this `%` through the shared divisor-cycle helper so repeated wrap-arounds avoid _currentRemainder = _currentRemainder.ReduceCycleRemainder(cycleLength);
// modulo operations and match the benchmarked fast path highlighted in
// MersenneDivisorCycleLengthGpuBenchmarks.
_currentRemainder %= cycleLength;
} }
} }
return _currentRemainder; return _currentRemainder;
@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
@@ -6,18 +7,20 @@ namespace PerfectNumbers.Core;
/// Tracks successive Montgomery residues for a single divisor while scanning exponent candidates on the CPU path. /// 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. /// Callers are responsible for seeding the stepper once per divisor and then advancing exponents in ascending order.
/// </summary> /// </summary>
internal struct ExponentRemainderStepperCpu(in MontgomeryDivisorData divisor) internal struct ExponentRemainderStepperCpu(in MontgomeryDivisorData divisor, ulong cycleLength = 0UL)
{ {
private readonly MontgomeryDivisorData _divisor = divisor; private readonly MontgomeryDivisorData _divisor = divisor;
private readonly ulong _modulus = divisor.Modulus; private readonly ulong _modulus = divisor.Modulus;
private readonly ulong _nPrime = divisor.NPrime; private readonly ulong _nPrime = divisor.NPrime;
private readonly ulong _montgomeryOne = divisor.MontgomeryOne; private readonly ulong _montgomeryOne = divisor.MontgomeryOne;
private readonly ulong _cycleLength = cycleLength;
private ulong PreviousExponent = 0UL; private ulong PreviousExponent = 0UL;
private ulong _currentMontgomery = divisor.MontgomeryOne; private ulong _currentMontgomery = divisor.MontgomeryOne;
private static readonly ConcurrentDictionary<(ulong Cycle, ulong Delta), ulong> _cycleDeltaCache = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool MatchesDivisor(in MontgomeryDivisorData divisor) public readonly bool MatchesDivisor(in MontgomeryDivisorData divisor, ulong cycleLength)
=> _modulus == divisor.Modulus && _nPrime == divisor.NPrime && _montgomeryOne == divisor.MontgomeryOne; => _modulus == divisor.Modulus && _nPrime == divisor.NPrime && _montgomeryOne == divisor.MontgomeryOne && _cycleLength == cycleLength;
/// <summary> /// <summary>
/// Clears the cached Montgomery residue so the stepper can be reused for another divisor. /// Clears the cached Montgomery residue so the stepper can be reused for another divisor.
@@ -65,10 +68,10 @@ private readonly MontgomeryDivisorData _divisor = divisor;
public ulong ComputeNextCpu(ulong exponent) public ulong ComputeNextCpu(ulong exponent)
{ {
ulong delta = exponent - PreviousExponent; ulong delta = exponent - PreviousExponent;
// TODO: Once divisor cycle lengths are mandatory, pull the delta multiplier from the ulong reducedDelta = ReduceDelta(delta);
// single-block divisor-cycle snapshot so we can skip the powmod entirely and reuse the ulong multiplier = reducedDelta == 0UL
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks. ? _montgomeryOne
ulong multiplier = delta.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor); : reducedDelta.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor);
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime); _currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime);
PreviousExponent = exponent; PreviousExponent = exponent;
return ReduceCurrent(); return ReduceCurrent();
@@ -85,9 +88,10 @@ private readonly MontgomeryDivisorData _divisor = divisor;
public bool ComputeNextIsUnity(ulong exponent) public bool ComputeNextIsUnity(ulong exponent)
{ {
ulong delta = exponent - PreviousExponent; ulong delta = exponent - PreviousExponent;
// TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle ulong reducedDelta = ReduceDelta(delta);
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor. ulong multiplier = reducedDelta == 0UL
ulong multiplier = delta.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor); ? _montgomeryOne
: reducedDelta.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor);
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime); _currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime);
PreviousExponent = exponent; PreviousExponent = exponent;
return _currentMontgomery == _montgomeryOne; return _currentMontgomery == _montgomeryOne;
@@ -121,10 +125,25 @@ private readonly MontgomeryDivisorData _divisor = divisor;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryAdvanceWithMontgomeryDelta(ulong exponent, ulong montgomeryDelta, out bool isUnity) 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 ulong delta = exponent - PreviousExponent;
// of relying on the caller-provided delta so incremental scans remain in sync with the snapshot ulong reducedDelta = ReduceDelta(delta);
// without computing additional cycles or mutating cache state.
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(montgomeryDelta, _modulus, _nPrime); // Prefer a cycle-reduced powmod so stepping stays aligned with the divisor-cycle cache.
ulong multiplier;
if (reducedDelta == 0UL)
{
multiplier = _montgomeryOne;
}
else if (_cycleLength != 0UL)
{
multiplier = reducedDelta.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor);
}
else
{
multiplier = montgomeryDelta;
}
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime);
PreviousExponent = exponent; PreviousExponent = exponent;
isUnity = _currentMontgomery == _montgomeryOne; isUnity = _currentMontgomery == _montgomeryOne;
return true; return true;
@@ -133,10 +152,33 @@ private readonly MontgomeryDivisorData _divisor = divisor;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void InitializeCpuState(ulong exponent) private void InitializeCpuState(ulong exponent)
{ {
_currentMontgomery = exponent.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor); ulong rotation = ReduceDelta(exponent);
_currentMontgomery = rotation == 0UL
? _montgomeryOne
: rotation.Pow2MontgomeryModWindowedKeepMontgomeryCpu(_divisor);
PreviousExponent = exponent; PreviousExponent = exponent;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly ulong ReduceCurrent() => _currentMontgomery.MontgomeryMultiplyCpu(1UL, _modulus, _nPrime); private readonly ulong ReduceCurrent() => _currentMontgomery.MontgomeryMultiplyCpu(1UL, _modulus, _nPrime);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly ulong ReduceDelta(ulong delta)
{
ulong cycleLength = _cycleLength;
if (cycleLength == 0UL)
{
return delta;
}
var key = (cycleLength, delta);
if (_cycleDeltaCache.TryGetValue(key, out ulong cached))
{
return cached;
}
ulong reduced = delta.ReduceCycleRemainder(cycleLength);
_cycleDeltaCache.TryAdd(key, reduced);
return reduced;
}
} }
+82 -63
View File
@@ -30,6 +30,23 @@ public sealed class HeuristicPrimeTester
private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
[ThreadStatic]
private static BidirectionalCycleRemainderStepper _mod10Stepper;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetMod10Remainder(ulong value)
{
if (_mod10Stepper.Modulus != 10UL)
{
_mod10Stepper = new BidirectionalCycleRemainderStepper(10UL);
_mod10Stepper.Initialize(value);
return (byte)_mod10Stepper.Step(value);
}
// Caller must initialize once per thread before first use.
return (byte)_mod10Stepper.Step(value);
}
internal enum HeuristicDivisorGroup : byte internal enum HeuristicDivisorGroup : byte
{ {
None = 0, None = 0,
@@ -70,7 +87,7 @@ public sealed class HeuristicPrimeTester
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPrimeCpu(ulong n) public static bool IsPrimeCpu(ulong n)
{ {
byte nMod10 = (byte)n.Mod10(); byte nMod10 = GetMod10Remainder(n);
ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n); ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
if (maxDivisorSquare < 9UL) if (maxDivisorSquare < 9UL)
@@ -85,7 +102,7 @@ public sealed class HeuristicPrimeTester
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n) public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n)
{ {
byte nMod10 = (byte)n.Mod10(); byte nMod10 = GetMod10Remainder(n);
ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n); ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
return IsPrimeGpu(gpu, n, maxDivisorSquare, nMod10); return IsPrimeGpu(gpu, n, maxDivisorSquare, nMod10);
} }
@@ -133,7 +150,7 @@ public sealed class HeuristicPrimeTester
} }
ulong divisor = groupADivisors[groupAIndex]; ulong divisor = groupADivisors[groupAIndex];
if (n % divisor == 0UL) if (n.ReduceCycleRemainder(divisor) == 0UL)
{ {
return false; return false;
} }
@@ -186,7 +203,7 @@ public sealed class HeuristicPrimeTester
} }
ulong divisor = groupADivisors[groupAIndex]; ulong divisor = groupADivisors[groupAIndex];
if (n % divisor == 0UL) if (n.ReduceCycleRemainder(divisor) == 0UL)
{ {
return false; return false;
} }
@@ -220,7 +237,7 @@ public sealed class HeuristicPrimeTester
break; break;
} }
if (n % divisor == 0UL) if (n.ReduceCycleRemainder(divisor) == 0UL)
{ {
return false; return false;
} }
@@ -241,7 +258,6 @@ public sealed class HeuristicPrimeTester
private static bool HeuristicTrialDivisionGpuDetectsDivisor(PrimeOrderCalculatorAccelerator gpu, ulong n, ulong maxDivisorSquare, byte nMod10) private static bool HeuristicTrialDivisionGpuDetectsDivisor(PrimeOrderCalculatorAccelerator gpu, ulong n, ulong maxDivisorSquare, byte nMod10)
{ {
// GpuPrimeWorkLimiter.Acquire();
int acceleratorIndex = gpu.AcceleratorIndex; int acceleratorIndex = gpu.AcceleratorIndex;
var kernelLauncher = gpu.HeuristicCombinedTrialDivisionKernelLauncher; var kernelLauncher = gpu.HeuristicCombinedTrialDivisionKernelLauncher;
bool compositeDetected; bool compositeDetected;
@@ -252,13 +268,6 @@ public sealed class HeuristicPrimeTester
var stream = AcceleratorStreamPool.Rent(acceleratorIndex); var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
flagView1DView.CopyFromCPU(stream, ref compositeFlag, 1); flagView1DView.CopyFromCPU(stream, ref compositeFlag, 1);
// Index1D index,
// ArrayView<int> resultFlag,
// ulong n,
// ulong maxDivisorSquare,
// HeuristicGpuDivisorTableKind tableKind,
// HeuristicGpuDivisorTables tables)
kernelLauncher( kernelLauncher(
stream, stream,
1, 1,
@@ -271,48 +280,9 @@ public sealed class HeuristicPrimeTester
flagView1DView.CopyToCPU(stream, ref compositeFlag, 1); flagView1DView.CopyToCPU(stream, ref compositeFlag, 1);
stream.Synchronize(); stream.Synchronize();
compositeDetected = compositeFlag != 0;
// compositeDetected = compositeFlag != 0;
// if (!compositeDetected)
// {
// ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10);
// for (int i = 0; i < endingOrder.Length && !compositeDetected; i++)
// {
// byte ending = endingOrder[i];
// HeuristicGpuDivisorTableKind tableKind = ending switch
// {
// 1 => HeuristicGpuDivisorTableKind.GroupBEnding1,
// 7 => HeuristicGpuDivisorTableKind.GroupBEnding7,
// 9 => HeuristicGpuDivisorTableKind.GroupBEnding9,
// _ => HeuristicGpuDivisorTableKind.GroupA,
// };
// if (tableKind == HeuristicGpuDivisorTableKind.GroupA)
// {
// continue;
// }
// int divisorLength = GetGroupBDivisors(ending).Length;
// compositeFlag = 0;
// flagView1D.CopyFromCPU(stream, ref compositeFlag, 1);
// kernelLauncher(
// stream,
// 1,
// flagView1D,
// n,
// maxDivisorSquare,
// tableKind,
// gpu.HeuristicGpuTables);
// flagView1D.CopyToCPU(stream, ref compositeFlag, 1);
// stream.Synchronize();
// compositeDetected = compositeFlag != 0;
// }
// }
AcceleratorStreamPool.Return(acceleratorIndex, stream); AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release(); compositeDetected = compositeFlag != 0;
return compositeDetected; return compositeDetected;
} }
@@ -556,6 +526,10 @@ public sealed class HeuristicPrimeTester
private readonly ReadOnlySpan<ulong> groupADivisorSquares; private readonly ReadOnlySpan<ulong> groupADivisorSquares;
private int groupAIndex; private int groupAIndex;
private Span<HeuristicGroupBSequenceState> groupBStates; private Span<HeuristicGroupBSequenceState> groupBStates;
private CycleRemainderStepper endingStepper;
private CycleRemainderStepper wheelStepper;
private bool hasRemainders;
private ulong lastDivisor;
public HeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span<HeuristicGroupBSequenceState> groupBBuffer) public HeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span<HeuristicGroupBSequenceState> groupBBuffer)
{ {
@@ -566,6 +540,10 @@ public sealed class HeuristicPrimeTester
int count = InitializeGroupBStates(nMod10, groupBBuffer); int count = InitializeGroupBStates(nMod10, groupBBuffer);
groupBStates = count == 0 ? Span<HeuristicGroupBSequenceState>.Empty : groupBBuffer[..count]; groupBStates = count == 0 ? Span<HeuristicGroupBSequenceState>.Empty : groupBBuffer[..count];
endingStepper = new CycleRemainderStepper(10UL);
wheelStepper = new CycleRemainderStepper(Wheel210);
hasRemainders = false;
lastDivisor = 0UL;
} }
public bool TryGetNext(out HeuristicDivisorCandidate candidate) public bool TryGetNext(out HeuristicDivisorCandidate candidate)
@@ -586,13 +564,14 @@ public sealed class HeuristicPrimeTester
HeuristicDivisorGroup group = currentIndex < GroupAConstantCount HeuristicDivisorGroup group = currentIndex < GroupAConstantCount
? HeuristicDivisorGroup.GroupAConstant ? HeuristicDivisorGroup.GroupAConstant
: HeuristicDivisorGroup.GroupAWheel; : HeuristicDivisorGroup.GroupAWheel;
(byte ending, ushort wheelResidue) = UpdateRemainders(divisor);
candidate = new HeuristicDivisorCandidate( candidate = new HeuristicDivisorCandidate(
divisor, divisor,
group, group,
(byte)(divisor % 10UL), ending,
0, 0,
(ushort)(divisor % Wheel210)); wheelResidue);
return true; return true;
} }
@@ -623,14 +602,35 @@ public sealed class HeuristicPrimeTester
ref HeuristicGroupBSequenceState bestState = ref groupBStates[bestIndex]; ref HeuristicGroupBSequenceState bestState = ref groupBStates[bestIndex];
bestState.Advance(); bestState.Advance();
(byte candidateEnding, ushort wheelResidueBest) = UpdateRemainders(bestCandidate);
candidate = new HeuristicDivisorCandidate( candidate = new HeuristicDivisorCandidate(
bestCandidate, bestCandidate,
HeuristicDivisorGroup.GroupB, HeuristicDivisorGroup.GroupB,
bestState.Ending, candidateEnding,
bestState.PriorityIndex, bestState.PriorityIndex,
(ushort)(bestCandidate % Wheel210)); wheelResidueBest);
return true; return true;
} }
private (byte ending, ushort wheelResidue) UpdateRemainders(ulong divisor)
{
byte ending;
ushort wheelResidue;
if (!hasRemainders)
{
ending = (byte)endingStepper.Initialize(divisor);
wheelResidue = (ushort)wheelStepper.Initialize(divisor);
hasRemainders = true;
}
else
{
ending = (byte)endingStepper.ComputeNext(divisor);
wheelResidue = (ushort)wheelStepper.ComputeNext(divisor);
}
lastDivisor = divisor;
return (ending, wheelResidue);
}
} }
@@ -684,6 +684,10 @@ public sealed class HeuristicPrimeTester
private readonly GpuUInt128 step; private readonly GpuUInt128 step;
private readonly GpuUInt128 limit; private readonly GpuUInt128 limit;
private GpuUInt128 current; private GpuUInt128 current;
private CycleRemainderStepper endingStepper;
private CycleRemainderStepper wheelStepper;
private byte currentEnding;
private ushort currentWheelResidue;
private MersenneDivisorResidueStepper residueStepper; private MersenneDivisorResidueStepper residueStepper;
private bool active; private bool active;
private ulong processedCount; private ulong processedCount;
@@ -705,6 +709,21 @@ public sealed class HeuristicPrimeTester
current = hasCandidates ? firstDivisor : GpuUInt128.Zero; current = hasCandidates ? firstDivisor : GpuUInt128.Zero;
residueStepper = hasCandidates ? new MersenneDivisorResidueStepper(exponent, stepLocal, firstDivisor) : default; residueStepper = hasCandidates ? new MersenneDivisorResidueStepper(exponent, stepLocal, firstDivisor) : default;
if (hasCandidates)
{
endingStepper = new CycleRemainderStepper(10UL);
currentEnding = (byte)endingStepper.Initialize(firstDivisor.Low);
wheelStepper = new CycleRemainderStepper(Wheel210);
currentWheelResidue = (ushort)wheelStepper.Initialize(firstDivisor.Low);
}
else
{
endingStepper = default;
wheelStepper = default;
currentEnding = 0;
currentWheelResidue = 0;
}
active = hasCandidates; active = hasCandidates;
processedCount = 0UL; processedCount = 0UL;
lastDivisor = 0UL; lastDivisor = 0UL;
@@ -724,7 +743,7 @@ public sealed class HeuristicPrimeTester
if (admissible) if (admissible)
{ {
candidate = CreateCandidate(value); candidate = CreateCandidate(value, currentEnding, currentWheelResidue);
return true; return true;
} }
} }
@@ -746,6 +765,8 @@ public sealed class HeuristicPrimeTester
current = next; current = next;
residueStepper.Advance(); residueStepper.Advance();
currentEnding = (byte)endingStepper.ComputeNext(next.Low);
currentWheelResidue = (ushort)wheelStepper.ComputeNext(next.Low);
} }
public readonly ulong ProcessedCount => processedCount; public readonly ulong ProcessedCount => processedCount;
@@ -754,9 +775,8 @@ public sealed class HeuristicPrimeTester
public readonly bool Exhausted => !active; public readonly bool Exhausted => !active;
private static HeuristicDivisorCandidate CreateCandidate(ulong value) private static HeuristicDivisorCandidate CreateCandidate(ulong value, byte ending, ushort wheelResidue)
{ {
byte ending = (byte)(value % 10UL);
HeuristicDivisorGroup group; HeuristicDivisorGroup group;
if (value == 3UL || value == 7UL || value == 11UL || value == 13UL) if (value == 3UL || value == 7UL || value == 11UL || value == 13UL)
@@ -772,8 +792,7 @@ public sealed class HeuristicPrimeTester
group = HeuristicDivisorGroup.GroupB; group = HeuristicDivisorGroup.GroupB;
} }
ushort residue = (ushort)(value % Wheel210); return new HeuristicDivisorCandidate(value, group, ending, 0, wheelResidue);
return new HeuristicDivisorCandidate(value, group, ending, 0, residue);
} }
} }
} }
+10 -11
View File
@@ -266,7 +266,7 @@ internal static partial class PrimeOrderCalculator
int exponent = entries[i].Value; int exponent = entries[i].Value;
for (int iteration = 0; iteration < exponent; iteration++) for (int iteration = 0; iteration < exponent; iteration++)
{ {
if ((order % primeFactor) != UInt128.Zero) if (order.ReduceCycleRemainder(primeFactor) != UInt128.Zero)
{ {
UInt128 candidate = order / primeFactor; UInt128 candidate = order / primeFactor;
if (Pow2ModWideCpu(candidate, prime, divisorData) == UInt128.One) if (Pow2ModWideCpu(candidate, prime, divisorData) == UInt128.One)
@@ -339,14 +339,13 @@ internal static partial class PrimeOrderCalculator
buffer[..length].Sort(static (a, b) => a.Value.CompareTo(b.Value)); buffer[..length].Sort(static (a, b) => a.Value.CompareTo(b.Value));
// TODO: Investigating using stepper to replace modulo operation
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
UInt128 primeFactor = buffer[i].Value; UInt128 primeFactor = buffer[i].Value;
int exponent = buffer[i].Exponent; int exponent = buffer[i].Exponent;
for (int iteration = 0; iteration < exponent; iteration++) for (int iteration = 0; iteration < exponent; iteration++)
{ {
if ((order % primeFactor) == UInt128.Zero) if (order.ReduceCycleRemainder(primeFactor) == UInt128.Zero)
{ {
UInt128 reduced = order / primeFactor; UInt128 reduced = order / primeFactor;
if (Pow2ModWideCpu(reduced, prime, divisorData) == UInt128.One) if (Pow2ModWideCpu(reduced, prime, divisorData) == UInt128.One)
@@ -405,7 +404,7 @@ internal static partial class PrimeOrderCalculator
UInt128 reduced = order; UInt128 reduced = order;
for (int iteration = 0; iteration < span[i].Exponent; iteration++) for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{ {
if ((reduced % primeFactor) != UInt128.Zero) if (reduced.ReduceCycleRemainder(primeFactor) != UInt128.Zero)
{ {
break; break;
} }
@@ -2583,7 +2582,7 @@ internal static partial class PrimeOrderCalculator
// continue; // continue;
// } // }
if ((remainingLocal % primeValue) != 0UL) if (remainingLocal.ReduceCycleRemainder(primeValue) != 0UL)
{ {
continue; continue;
} }
@@ -2745,16 +2744,16 @@ internal static partial class PrimeOrderCalculator
private static ulong AdvancePolynomialCpu(ulong x, ulong c, ulong modulus) private static ulong AdvancePolynomialCpu(ulong x, ulong c, ulong modulus)
{ {
UInt128 value = (UInt128)x * x + c; UInt128 value = (UInt128)x * x + c;
return (ulong)(value % modulus); return (ulong)value.ReduceCycleRemainder(modulus);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong AdvancePolynomialTwiceCpu(ulong x, ulong c, ulong modulus) private static ulong AdvancePolynomialTwiceCpu(ulong x, ulong c, ulong modulus)
{ {
UInt128 value = (UInt128)x * x + c; UInt128 value = (UInt128)x * x + c;
var remainder = (ulong)(value % modulus); var remainder = (ulong)value.ReduceCycleRemainder(modulus);
value = (UInt128)remainder * remainder + c; value = (UInt128)remainder * remainder + c;
return (ulong)(value % modulus); return (ulong)value.ReduceCycleRemainder(modulus);
} }
private static ulong BinaryGcd(ulong a, ulong b) private static ulong BinaryGcd(ulong a, ulong b)
@@ -2897,7 +2896,7 @@ internal static partial class PrimeOrderCalculator
int exponent = entries[i].Value; int exponent = entries[i].Value;
for (int iteration = 0; iteration < exponent; iteration++) for (int iteration = 0; iteration < exponent; iteration++)
{ {
ulong remainder = order % primeFactor; ulong remainder = order.ReduceCycleRemainder(primeFactor);
if (remainder != 0UL) if (remainder != 0UL)
{ {
break; break;
@@ -2951,7 +2950,7 @@ internal static partial class PrimeOrderCalculator
{ {
int exponent = 1; int exponent = 1;
ulong remaining = quotient; ulong remaining = quotient;
while ((remaining % factor) == 0UL) while (remaining.ReduceCycleRemainder(factor) == 0UL)
{ {
remaining /= factor; remaining /= factor;
exponent++; exponent++;
@@ -3062,7 +3061,7 @@ internal static partial class PrimeOrderCalculator
UInt128 reduced = candidate; UInt128 reduced = candidate;
for (int iteration = 0; iteration < span[i].Exponent; iteration++) for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{ {
if ((reduced % primeFactor) != UInt128.Zero) if (reduced.ReduceCycleRemainder(primeFactor) != UInt128.Zero)
{ {
break; break;
} }
@@ -309,7 +309,7 @@ internal static partial class PrimeOrderCalculator
UInt128 reduced = order; UInt128 reduced = order;
for (int iteration = 0; iteration < span[i].Exponent; iteration++) for (int iteration = 0; iteration < span[i].Exponent; iteration++)
{ {
if ((reduced % primeFactor) != UInt128.Zero) if (reduced.ReduceCycleRemainder(primeFactor) != UInt128.Zero)
{ {
break; break;
} }
+2 -4
View File
@@ -1,7 +1,5 @@
using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.Gpu.Accelerators;
@@ -72,7 +70,7 @@ public sealed class PrimeTester
} }
divisor = smallPrimeDivisors[i]; divisor = smallPrimeDivisors[i];
uint deltaMod = delta >= divisor ? (uint)(delta % divisor) : (uint)delta; uint deltaMod = delta.ReduceCycleRemainder(divisor);
remainder = remainders[i] + deltaMod; remainder = remainders[i] + deltaMod;
if (remainder >= divisor) if (remainder >= divisor)
{ {
@@ -105,7 +103,7 @@ public sealed class PrimeTester
} }
divisor = smallPrimeDivisors[i]; divisor = smallPrimeDivisors[i];
remainder = (uint)(n % divisor); remainder = n.ReduceCycleRemainder(divisor);
remainders[i] = remainder; remainders[i] = remainder;
if (remainder == 0UL) if (remainder == 0UL)
+21 -3
View File
@@ -1,11 +1,29 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
public sealed class PrimeTesterByLastDigit public sealed class PrimeTesterByLastDigit
{ {
[ThreadStatic]
private static BidirectionalCycleRemainderStepper _mod10Stepper;
[ThreadStatic]
private static bool _mod10Initialized;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetMod10(ulong value)
{
if (!_mod10Initialized || _mod10Stepper.Modulus != 10UL)
{
_mod10Stepper = new BidirectionalCycleRemainderStepper(10UL);
_mod10Stepper.Initialize(value);
_mod10Initialized = true;
return (byte)_mod10Stepper.Step(value);
}
return (byte)_mod10Stepper.Step(value);
}
public static bool IsPrimeCpu(ulong n) public static bool IsPrimeCpu(ulong n)
{ {
// The below IFs never trigger in production code of EvenPerfectBitScanner on --mersenne=bydivisor path. // The below IFs never trigger in production code of EvenPerfectBitScanner on --mersenne=bydivisor path.
@@ -27,7 +45,7 @@ public sealed class PrimeTesterByLastDigit
uint[] smallPrimeDivisors; uint[] smallPrimeDivisors;
ulong[] smallPrimeDivisorsMul; ulong[] smallPrimeDivisorsMul;
byte nMod10 = n.Mod10(); byte nMod10 = GetMod10(n);
switch (nMod10) switch (nMod10)
{ {
+3 -3
View File
@@ -360,9 +360,9 @@ namespace PerfectNumbers.Core
private static bool _hasExponentRemainderStepperCpu; private static bool _hasExponentRemainderStepperCpu;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ExponentRemainderStepperCpu RentExponentStepperCpu(in MontgomeryDivisorData divisorData) internal static ExponentRemainderStepperCpu RentExponentStepperCpu(in MontgomeryDivisorData divisorData, ulong cycleLength = 0UL)
{ {
if (_hasExponentRemainderStepperCpu && _exponentRemainderStepperCpu.MatchesDivisor(divisorData)) if (_hasExponentRemainderStepperCpu && _exponentRemainderStepperCpu.MatchesDivisor(divisorData, cycleLength))
{ {
ExponentRemainderStepperCpu stepper = _exponentRemainderStepperCpu; ExponentRemainderStepperCpu stepper = _exponentRemainderStepperCpu;
_hasExponentRemainderStepperCpu = false; _hasExponentRemainderStepperCpu = false;
@@ -370,7 +370,7 @@ namespace PerfectNumbers.Core
return stepper; return stepper;
} }
return new ExponentRemainderStepperCpu(divisorData); return new ExponentRemainderStepperCpu(divisorData, cycleLength);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
+47
View File
@@ -306,6 +306,53 @@ public static class UInt128Extensions
return remainder.Pow2MontgomeryModWindowed(modulus); return remainder.Pow2MontgomeryModWindowed(modulus);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UInt128 ReduceCycleRemainder(this UInt128 value, UInt128 modulus)
{
if (modulus == UInt128.Zero || value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
return value % modulus;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool ShouldUseSingleBit(UInt128 exponent) => (exponent >> 64) == UInt128.Zero && (ulong)exponent <= Pow2WindowFallbackThreshold; private static bool ShouldUseSingleBit(UInt128 exponent) => (exponent >> 64) == UInt128.Zero && (ulong)exponent <= Pow2WindowFallbackThreshold;
+51 -1
View File
@@ -17,5 +17,55 @@ public static class UIntExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Mod8(this uint value) => value & 7U; public static uint Mod8(this uint value) => value & 7U;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReduceCycleRemainder(this uint value, uint modulus)
{
if (modulus == 0U || value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
return value % modulus;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReduceCycleRemainder(this ulong value, uint modulus)
=> (uint)value.ReduceCycleRemainder((ulong)modulus);
}
+50 -2
View File
@@ -7,8 +7,6 @@ namespace PerfectNumbers.Core;
public static partial class ULongExtensions public static partial class ULongExtensions
{ {
private const ulong Pow2WindowFallbackThreshold = 32UL; private const ulong Pow2WindowFallbackThreshold = 32UL;
private const int Pow2WindowOddPowerCount = 1 << (PerfectNumberConstants.Pow2WindowSize - 1);
private static readonly UInt128 Pow2WindowedModulusThreshold = (UInt128)PerfectNumberConstants.MaxQForDivisorCycles; private static readonly UInt128 Pow2WindowedModulusThreshold = (UInt128)PerfectNumberConstants.MaxQForDivisorCycles;
private static readonly byte[] BitStatsZeroCount = new byte[256]; private static readonly byte[] BitStatsZeroCount = new byte[256];
@@ -1135,6 +1133,56 @@ public static partial class ULongExtensions
return x1; return x1;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ReduceCycleRemainder(this ulong value, ulong modulus)
{
// BigIntegerExtensions.Track(modulus);
// BigIntegerExtensions.DumpCycleRemainderStats();
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
value -= modulus;
if (value < modulus)
{
return value;
}
return value % modulus;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool SharesFactorWithExponentMinusOneCpu(this ulong exponent) public static bool SharesFactorWithExponentMinusOneCpu(this ulong exponent)
{ {