using System.Buffers.Binary;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System.Numerics;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
namespace EvenPerfectBitScanner.Benchmarks;
///
/// Benchmark results collected on AMD Ryzen 5 5625U (.NET 8.0, launchCount: 1, warmupCount: 1, iterationCount: 5):
/// Small sample (~1e6 random odd moduli)
/// - MontgomeryWithoutCycle: 35.41 µs (baseline)
/// - MontgomeryWithPrecomputedCycle: 25.81 µs (0.73× baseline)
/// - MontgomeryWithGpuCycleComputation: 54.09 ms (1,527× baseline, includes GPU cycle calculation)
/// Large sample (Mersenne-like moduli from 2^48-1 to 2^63-1)
/// - MontgomeryWithoutCycle: 117.33 µs (baseline)
/// - MontgomeryWithPrecomputedCycle: 9.12 µs (0.08× baseline)
/// - MontgomeryWithGpuCycleComputation: 12.91 µs (0.11× baseline)
///
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
public class Pow2ModBenchmarks
{
private const int SampleCount = 256;
private const ulong OneShiftedLeft63 = 1UL << 63;
private const long OneShiftedLeft60 = 1L << 60;
private const ulong Pow2WindowFallbackThreshold = 32UL;
private MontgomeryDivisorData[] _smallDivisors = Array.Empty();
private MontgomeryDivisorData[] _largeDivisors = Array.Empty();
private MontgomeryDivisorData[] _veryLargeDivisors = Array.Empty();
private UInt128[] _wideModuli = Array.Empty();
private UInt128[] _wideExponents = Array.Empty();
private ulong[] _smallExponents = Array.Empty();
private ulong[] _largeExponents = Array.Empty();
private ulong[] _veryLargeExponents = Array.Empty();
private ulong[] _smallCycles = Array.Empty();
private ulong[] _largeCycles = Array.Empty();
private ulong[] _veryLargeCycles = Array.Empty();
private UInt128[] _wideCycles = Array.Empty();
private ulong? _previousPrimeOrder;
private UInt128? _previousWidePrimeOrder;
public enum InputScale
{
Small,
Large,
VeryLarge
}
[ParamsAllValues]
public InputScale Scale { get; set; }
[GlobalSetup]
public void Setup()
{
SampleCollections cache = SampleCollectionsProvider.Instance;
_smallDivisors = cache.SmallDivisors;
_largeDivisors = cache.LargeDivisors;
_veryLargeDivisors = cache.VeryLargeDivisors;
_wideModuli = cache.WideModuli;
_wideExponents = cache.WideExponents;
_smallExponents = cache.SmallExponents;
_largeExponents = cache.LargeExponents;
_veryLargeExponents = cache.VeryLargeExponents;
_smallCycles = cache.SmallCycles;
_largeCycles = cache.LargeCycles;
_veryLargeCycles = cache.VeryLargeCycles;
_wideCycles = cache.WideCycles;
}
///
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
///
[Benchmark]
public ulong MontgomeryWindowedCpu()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
}
return checksum;
}
///
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
///
/// GPU benchmark failed running, hanging up early before reaching workload testing.
///
// [Benchmark]
public ulong MontgomeryWindowedGpu()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]);
}
return checksum;
}
///
/// 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).
///
[Benchmark]
public ulong MontgomeryWindowedWithCycleCpu()
{
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]);
}
return checksum;
}
///
/// 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).
///
/// GPU benchmark failed running, hanging up early before reaching workload testing.
///
// [Benchmark]
public ulong MontgomeryWindowedWithCycleGpu()
{
GetData(out var exponents, out var divisors, out var cycles);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
}
return checksum;
}
///
/// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
///
/// GPU benchmark failed running, hanging up early before reaching workload testing.
///
// [Benchmark]
public ulong MontgomeryWithGpuCycleComputationCpu()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
}
return checksum;
}
///
/// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
///
/// GPU benchmark failed running, hanging up early before reaching workload testing.
///
// [Benchmark]
public ulong MontgomeryWithGpuCycleComputationGpu()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
}
return checksum;
}
[Benchmark]
public ulong MontgomeryWithHeuristicCycleComputationCpu()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
}
return checksum;
}
///
/// GPU benchmark failed running, hanging up early before reaching workload testing.
///
///
/// // [Benchmark]
public ulong MontgomeryWithHeuristicCycleComputationGpu()
{
GetData(out var exponents, out var divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
}
return checksum;
}
[Benchmark]
public UInt128 HeuristicWideCycleComputation()
{
UInt128 checksum = UInt128.Zero;
for (int i = 0; i < SampleCount; i++)
{
UInt128 cycle = CalculateCycleLengthWithHeuristics(_wideModuli[i]);
checksum ^= cycle;
}
return checksum;
}
///
/// 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×).
///
[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]);
}
return checksum;
}
///
/// 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×).
///
[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]);
}
return checksum;
}
///
/// 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×).
///
[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]);
}
return checksum;
}
///
/// 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×).
///
[Benchmark(Baseline = true)]
public ulong StandardPow2Mod()
{
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);
}
return checksum;
}
///
/// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×).
///
[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);
}
return checksum;
}
///
/// Relies on ; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×).
///
[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);
}
return checksum;
}
///
/// Current UInt128-native windowed Montgomery ladder that avoids GpuUInt128 on CPU paths.
///
[Benchmark]
public UInt128 MontgomeryWindowedUInt128()
{
UInt128 checksum = UInt128.Zero;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= _wideExponents[i].Pow2MontgomeryModWindowed(_wideModuli[i]);
}
return checksum;
}
///
/// Previous CPU shim that reused the GPU-oriented GpuUInt128 implementation.
///
[Benchmark]
public UInt128 MontgomeryWindowedGpuShimUInt128()
{
UInt128 checksum = UInt128.Zero;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= Pow2MontgomeryModWindowedGpuShim(_wideExponents[i], _wideModuli[i]);
}
return checksum;
}
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
{
switch (Scale)
{
case InputScale.Small:
exponents = _smallExponents;
divisors = _smallDivisors;
cycles = _smallCycles;
break;
case InputScale.Large:
exponents = _largeExponents;
divisors = _largeDivisors;
cycles = _largeCycles;
break;
case InputScale.VeryLarge:
exponents = _veryLargeExponents;
divisors = _veryLargeDivisors;
cycles = _veryLargeCycles;
break;
default:
throw new ArgumentOutOfRangeException(nameof(Scale), Scale, null);
}
}
private sealed class SampleCollections
{
public SampleCollections(
MontgomeryDivisorData[] smallDivisors,
MontgomeryDivisorData[] largeDivisors,
MontgomeryDivisorData[] veryLargeDivisors,
UInt128[] wideModuli,
UInt128[] wideExponents,
ulong[] smallExponents,
ulong[] largeExponents,
ulong[] veryLargeExponents,
ulong[] smallCycles,
ulong[] largeCycles,
ulong[] veryLargeCycles,
UInt128[] wideCycles)
{
SmallDivisors = smallDivisors;
LargeDivisors = largeDivisors;
VeryLargeDivisors = veryLargeDivisors;
WideModuli = wideModuli;
WideExponents = wideExponents;
SmallExponents = smallExponents;
LargeExponents = largeExponents;
VeryLargeExponents = veryLargeExponents;
SmallCycles = smallCycles;
LargeCycles = largeCycles;
VeryLargeCycles = veryLargeCycles;
WideCycles = wideCycles;
}
public MontgomeryDivisorData[] SmallDivisors { get; }
public MontgomeryDivisorData[] LargeDivisors { get; }
public MontgomeryDivisorData[] VeryLargeDivisors { get; }
public UInt128[] WideModuli { get; }
public UInt128[] WideExponents { get; }
public ulong[] SmallExponents { get; }
public ulong[] LargeExponents { get; }
public ulong[] VeryLargeExponents { get; }
public ulong[] SmallCycles { get; }
public ulong[] LargeCycles { get; }
public ulong[] VeryLargeCycles { get; }
public UInt128[] WideCycles { get; }
}
private static class SampleCollectionsProvider
{
private static readonly Lazy Cache = new(Create);
public static SampleCollections Instance => Cache.Value;
private static SampleCollections Create()
{
Console.WriteLine("Preparing power-of-two modulus benchmark sample collections...");
Random random = new(13);
Random heuristicRandom = new(113);
Random wideRandom = new(19);
var smallDivisors = new MontgomeryDivisorData[SampleCount];
var largeDivisors = new MontgomeryDivisorData[SampleCount];
var veryLargeDivisors = new MontgomeryDivisorData[SampleCount];
var wideModuli = new UInt128[SampleCount];
var wideExponents = new UInt128[SampleCount];
var smallExponents = new ulong[SampleCount];
var largeExponents = new ulong[SampleCount];
var veryLargeExponents = new ulong[SampleCount];
var smallCycles = new ulong[SampleCount];
var largeCycles = new ulong[SampleCount];
var veryLargeCycles = new ulong[SampleCount];
var wideCycles = new UInt128[SampleCount];
ulong? previousPrimeOrder = null;
UInt128? previousWidePrimeOrder = null;
for (int i = 0; i < SampleCount; i++)
{
ulong smallModulus = NextSmallOddModulus(random);
Console.WriteLine($"Calculating small cycle {smallModulus}");
smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
smallExponents[i] = NextSmallExponent(random);
smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle(random);
Console.WriteLine($"Calculating large cycle {largeModulus}");
largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
largeExponents[i] = NextLargeExponent(random);
largeCycles[i] = largeCycle;
ulong veryLargeModulus = NextVeryLargeOddModulus(heuristicRandom);
Console.WriteLine($"Calculating very large cycle {veryLargeModulus} with heuristics");
veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus);
veryLargeExponents[i] = NextVeryLargeExponent(heuristicRandom);
veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus, ref previousPrimeOrder);
UInt128 wideModulus = NextWideOddModulus(wideRandom);
Console.WriteLine($"Calculating wide cycle {wideModulus} with heuristics");
wideModuli[i] = wideModulus;
wideExponents[i] = NextWideExponent(wideRandom);
wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus, ref previousWidePrimeOrder);
}
SampleCollections collections = new SampleCollections(
smallDivisors,
largeDivisors,
veryLargeDivisors,
wideModuli,
wideExponents,
smallExponents,
largeExponents,
veryLargeExponents,
smallCycles,
largeCycles,
veryLargeCycles,
wideCycles);
Console.WriteLine("Finished preparing power-of-two modulus benchmark sample collections.");
return collections;
}
private static ulong NextSmallExponent(Random random) => (ulong)random.NextInt64(1L, 1_000_000L);
private static ulong NextLargeExponent(Random random) => (ulong)random.NextInt64(OneShiftedLeft60, long.MaxValue);
private static ulong NextVeryLargeExponent(Random random)
{
Span buffer = stackalloc byte[8];
random.NextBytes(buffer);
ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63);
return value;
}
private static ulong NextSmallOddModulus(Random random)
{
ulong value = (ulong)random.NextInt64(3L, 1_000_000L);
return value | 1UL;
}
private static (ulong Modulus, ulong CycleLength) NextLargeModulusAndCycle(Random random)
{
int bitLength = random.Next(48, 64);
ulong modulus = (1UL << bitLength) - 1UL;
return (modulus, (ulong)bitLength);
}
private static ulong NextVeryLargeOddModulus(Random random)
{
Span buffer = stackalloc byte[8];
random.NextBytes(buffer);
ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL;
return value;
}
private static UInt128 NextWideOddModulus(Random random)
{
Span buffer = stackalloc byte[16];
random.NextBytes(buffer);
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]);
if (high == 0UL)
{
high = 1UL;
}
UInt128 modulus = ((UInt128)high << 64) | low;
modulus |= UInt128.One;
if (modulus <= UInt128.One)
{
modulus += 2UL;
}
return modulus;
}
private static UInt128 NextWideExponent(Random random)
{
Span buffer = stackalloc byte[16];
random.NextBytes(buffer);
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]);
if (high == 0UL)
{
high = 1UL;
}
return ((UInt128)high << 64) | low;
}
private static ulong CalculateCycleLengthWithHeuristics(ulong modulus, ref ulong? previousPrimeOrder)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
}
ulong order = PrimeOrderCalculator.Calculate(
modulus,
previousPrimeOrder,
divisorData,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
if (order != 0UL)
{
previousPrimeOrder = order;
return order;
}
previousPrimeOrder = null;
return MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
}
private static UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus, ref UInt128? previousWidePrimeOrder)
{
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
{
return MersenneDivisorCycles.GetCycle(modulus);
}
UInt128 order = PrimeOrderCalculator.Calculate(
modulus,
previousWidePrimeOrder,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
if (order != UInt128.Zero)
{
previousWidePrimeOrder = order;
return order;
}
previousWidePrimeOrder = null;
return MersenneDivisorCycles.GetCycle(modulus);
}
}
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);
return new MontgomeryDivisorData(
modulus,
nPrime,
montgomeryOne,
montgomeryTwo,
montgomeryTwoSquared);
}
private ulong CalculateCycleLengthWithHeuristics(ulong modulus, ulong fallbackOrder = 0UL)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
}
#if DEBUG
Console.WriteLine("Trying heuristic. Prime order calculation");
#endif
ulong order = PrimeOrderCalculator.Calculate(
modulus,
_previousPrimeOrder,
divisorData,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
if (order != 0UL)
{
_previousPrimeOrder = order;
return order;
}
_previousPrimeOrder = null;
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
}
private UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus)
{
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
{
return MersenneDivisorCycles.GetCycle(modulus);
}
#if DEBUG
Console.WriteLine("Trying heuristic. Wide prime order calculation");
#endif
UInt128 order = PrimeOrderCalculator.Calculate(
modulus,
_previousWidePrimeOrder,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
if (order != UInt128.Zero)
{
_previousWidePrimeOrder = order;
return order;
}
_previousWidePrimeOrder = null;
Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation");
return MersenneDivisorCycles.GetCycle(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 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;
if (exponent == 0UL)
{
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
{
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
if (((exponent >> bitIndex) & 1UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(baseVal, 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;
}
ulong result = divisor.MontgomeryOne;
ulong baseVal = divisor.MontgomeryTwo;
ulong nPrime = divisor.NPrime;
ulong remainingExponent = exponent;
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);
if ((nibble & 1UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
}
if ((nibble & 2UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
}
if ((nibble & 4UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
}
if ((nibble & 8UL) != 0UL)
{
result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
}
baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
remainingExponent >>= 4;
}
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
{
ulong modulus = divisor.Modulus;
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
ulong nPrime = divisor.NPrime;
ulong result = divisor.MontgomeryOne;
ulong baseVal = divisor.MontgomeryTwo;
if (exponent == 0UL)
{
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
const int WindowSize = 5;
Span 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);
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);
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);
}
result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
bitIndex = windowStart - 1;
}
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
}
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;
while (remainingExponent > 0UL)
{
if ((remainingExponent & 1UL) != 0UL)
{
result = MultiplyMod(result, baseVal, modulus);
}
remainingExponent >>= 1;
if (remainingExponent == 0UL)
{
break;
}
baseVal = MultiplyMod(baseVal, baseVal, modulus);
}
return result;
}
private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus)
{
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return 0UL;
}
if (exponent == 0UL)
{
return 1UL % modulus;
}
Span 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);
}
ulong result = 1UL % modulus;
ulong remainingExponent = exponent;
int bitIndex = 0;
while (remainingExponent > 0UL)
{
if ((remainingExponent & 1UL) != 0UL)
{
result = MultiplyMod(result, powers[bitIndex], modulus);
}
remainingExponent >>= 1;
bitIndex++;
}
return result;
}
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;
}
private static UInt128 Pow2MontgomeryModWindowedGpuShim(UInt128 exponent, UInt128 modulus)
{
if (modulus == UInt128.One)
{
return UInt128.Zero;
}
if (exponent == UInt128.Zero)
{
return UInt128.One % modulus;
}
GpuUInt128 modulusGpu = new(modulus);
GpuUInt128 exponentGpu = new(exponent);
GpuUInt128 baseValue = new(2UL);
if (baseValue.CompareTo(modulusGpu) >= 0)
{
baseValue.Sub(modulusGpu);
}
if (ShouldUseSingleBit(exponentGpu))
{
GpuUInt128 singleBitResult = Pow2MontgomeryModSingleBit(exponentGpu, modulusGpu, baseValue);
return (UInt128)singleBitResult;
}
int bitLength = exponentGpu.GetBitLength();
int windowSize = GetWindowSize(bitLength);
int oddPowerCount = 1 << (windowSize - 1);
Span oddPowers = stackalloc GpuUInt128[oddPowerCount];
InitializeOddPowers(baseValue, modulusGpu, oddPowers);
GpuUInt128 result = GpuUInt128.One;
int index = bitLength - 1;
while (index >= 0)
{
if (!IsBitSet(exponentGpu, index))
{
result.MulMod(result, modulusGpu);
index--;
continue;
}
int windowStart = index - windowSize + 1;
if (windowStart < 0)
{
windowStart = 0;
}
while (!IsBitSet(exponentGpu, windowStart))
{
windowStart++;
}
int windowBitCount = index - windowStart + 1;
for (int square = 0; square < windowBitCount; square++)
{
result.MulMod(result, modulusGpu);
}
ulong windowValue = ExtractWindowValue(exponentGpu, windowStart, windowBitCount);
int tableIndex = (int)((windowValue - 1UL) >> 1);
GpuUInt128 factor = oddPowers[tableIndex];
result.MulMod(factor, modulusGpu);
index = windowStart - 1;
}
return (UInt128)result;
}
private static bool ShouldUseSingleBit(GpuUInt128 exponent)
{
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
{
return true;
}
return false;
}
private static int GetWindowSize(int bitLength)
{
if (bitLength <= 8)
{
return Math.Max(bitLength, 1);
}
if (bitLength <= 23)
{
return 4;
}
if (bitLength <= 79)
{
return 5;
}
if (bitLength <= 239)
{
return 6;
}
if (bitLength <= 671)
{
return 7;
}
return 8;
}
private static void InitializeOddPowers(GpuUInt128 baseValue, GpuUInt128 modulus, Span oddPowers)
{
oddPowers[0] = baseValue;
if (oddPowers.Length == 1)
{
return;
}
baseValue.MulMod(baseValue, modulus);
for (int i = 1; i < oddPowers.Length; i++)
{
oddPowers[i] = oddPowers[i - 1];
oddPowers[i].MulMod(baseValue, modulus);
}
}
private static GpuUInt128 Pow2MontgomeryModSingleBit(GpuUInt128 exponent, GpuUInt128 modulus, GpuUInt128 baseValue)
{
GpuUInt128 result = GpuUInt128.One;
while (!exponent.IsZero)
{
if ((exponent.Low & 1UL) != 0UL)
{
result.MulMod(baseValue, modulus);
}
exponent.ShiftRight(1);
if (exponent.IsZero)
{
break;
}
baseValue.MulMod(baseValue, modulus);
}
return result;
}
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
{
if (bitIndex >= 64)
{
return ((value.High >> (bitIndex - 64)) & 1UL) != 0UL;
}
return ((value.Low >> bitIndex) & 1UL) != 0UL;
}
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
{
if (windowStart != 0)
{
GpuUInt128 shifted = exponent;
shifted.ShiftRight(windowStart);
ulong mask = (1UL << windowBitCount) - 1UL;
return shifted.Low & mask;
}
ulong directMask = (1UL << windowBitCount) - 1UL;
return exponent.Low & directMask;
}
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
{
UInt128 product = (UInt128)x * y;
return (ulong)(product % modulus);
}
}