Restore Pow2 benchmark data reuse
This commit is contained in:
1 parent
b2ed0d5832
commit
c6ad53fffb
3 files changed
+260
-322
No files matched your search
+258
-12
@@ -4,6 +4,7 @@ using BenchmarkDotNet.Attributes;
|
|||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
@@ -20,16 +21,18 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||||
public class Pow2MontgomeryModBenchmarks
|
public class Pow2ModBenchmarks
|
||||||
{
|
{
|
||||||
private const int SampleCount = 256;
|
private const int SampleCount = 256;
|
||||||
private const ulong OneShiftedLeft63 = 1UL << 63;
|
private const ulong OneShiftedLeft63 = 1UL << 63;
|
||||||
private const long OneShiftedLeft60 = 1L << 60;
|
private const long OneShiftedLeft60 = 1L << 60;
|
||||||
|
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
||||||
|
|
||||||
private MontgomeryDivisorData[] _smallDivisors = Array.Empty<MontgomeryDivisorData>();
|
private MontgomeryDivisorData[] _smallDivisors = Array.Empty<MontgomeryDivisorData>();
|
||||||
private MontgomeryDivisorData[] _largeDivisors = Array.Empty<MontgomeryDivisorData>();
|
private MontgomeryDivisorData[] _largeDivisors = Array.Empty<MontgomeryDivisorData>();
|
||||||
private MontgomeryDivisorData[] _veryLargeDivisors = Array.Empty<MontgomeryDivisorData>();
|
private MontgomeryDivisorData[] _veryLargeDivisors = Array.Empty<MontgomeryDivisorData>();
|
||||||
private UInt128[] _wideModuli = Array.Empty<UInt128>();
|
private UInt128[] _wideModuli = Array.Empty<UInt128>();
|
||||||
|
private UInt128[] _wideExponents = Array.Empty<UInt128>();
|
||||||
private ulong[] _smallExponents = Array.Empty<ulong>();
|
private ulong[] _smallExponents = Array.Empty<ulong>();
|
||||||
private ulong[] _largeExponents = Array.Empty<ulong>();
|
private ulong[] _largeExponents = Array.Empty<ulong>();
|
||||||
private ulong[] _veryLargeExponents = Array.Empty<ulong>();
|
private ulong[] _veryLargeExponents = Array.Empty<ulong>();
|
||||||
@@ -58,6 +61,7 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
_largeDivisors = cache.LargeDivisors;
|
_largeDivisors = cache.LargeDivisors;
|
||||||
_veryLargeDivisors = cache.VeryLargeDivisors;
|
_veryLargeDivisors = cache.VeryLargeDivisors;
|
||||||
_wideModuli = cache.WideModuli;
|
_wideModuli = cache.WideModuli;
|
||||||
|
_wideExponents = cache.WideExponents;
|
||||||
_smallExponents = cache.SmallExponents;
|
_smallExponents = cache.SmallExponents;
|
||||||
_largeExponents = cache.LargeExponents;
|
_largeExponents = cache.LargeExponents;
|
||||||
_veryLargeExponents = cache.VeryLargeExponents;
|
_veryLargeExponents = cache.VeryLargeExponents;
|
||||||
@@ -70,8 +74,8 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark(Baseline = true)]
|
[Benchmark]
|
||||||
public ulong BaselineCpu()
|
public ulong MontgomeryWindowedCpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -88,7 +92,7 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BaselineGpu()
|
public ulong MontgomeryWindowedGpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -105,7 +109,7 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BaselineWithKnownCycleCpu()
|
public ulong MontgomeryWindowedWithCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out var cycles);
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -122,7 +126,7 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BaselineWithKnownCycleGpu()
|
public ulong MontgomeryWindowedWithCycleGpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out var cycles);
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -271,8 +275,8 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×).
|
/// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark(Baseline = true)]
|
||||||
public ulong BinaryMod()
|
public ulong StandardPow2Mod()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -319,6 +323,38 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current UInt128-native windowed Montgomery ladder that avoids GpuUInt128 on CPU paths.
|
||||||
|
/// </summary>
|
||||||
|
[Benchmark]
|
||||||
|
public UInt128 MontgomeryWindowedUInt128()
|
||||||
|
{
|
||||||
|
UInt128 checksum = UInt128.Zero;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= _wideExponents[i].Pow2MontgomeryModWindowed(_wideModuli[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Previous CPU shim that reused the GPU-oriented GpuUInt128 implementation.
|
||||||
|
/// </summary>
|
||||||
|
[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)
|
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
||||||
{
|
{
|
||||||
switch (Scale)
|
switch (Scale)
|
||||||
@@ -350,6 +386,7 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
MontgomeryDivisorData[] largeDivisors,
|
MontgomeryDivisorData[] largeDivisors,
|
||||||
MontgomeryDivisorData[] veryLargeDivisors,
|
MontgomeryDivisorData[] veryLargeDivisors,
|
||||||
UInt128[] wideModuli,
|
UInt128[] wideModuli,
|
||||||
|
UInt128[] wideExponents,
|
||||||
ulong[] smallExponents,
|
ulong[] smallExponents,
|
||||||
ulong[] largeExponents,
|
ulong[] largeExponents,
|
||||||
ulong[] veryLargeExponents,
|
ulong[] veryLargeExponents,
|
||||||
@@ -362,6 +399,7 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
LargeDivisors = largeDivisors;
|
LargeDivisors = largeDivisors;
|
||||||
VeryLargeDivisors = veryLargeDivisors;
|
VeryLargeDivisors = veryLargeDivisors;
|
||||||
WideModuli = wideModuli;
|
WideModuli = wideModuli;
|
||||||
|
WideExponents = wideExponents;
|
||||||
SmallExponents = smallExponents;
|
SmallExponents = smallExponents;
|
||||||
LargeExponents = largeExponents;
|
LargeExponents = largeExponents;
|
||||||
VeryLargeExponents = veryLargeExponents;
|
VeryLargeExponents = veryLargeExponents;
|
||||||
@@ -375,6 +413,7 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
public MontgomeryDivisorData[] LargeDivisors { get; }
|
public MontgomeryDivisorData[] LargeDivisors { get; }
|
||||||
public MontgomeryDivisorData[] VeryLargeDivisors { get; }
|
public MontgomeryDivisorData[] VeryLargeDivisors { get; }
|
||||||
public UInt128[] WideModuli { get; }
|
public UInt128[] WideModuli { get; }
|
||||||
|
public UInt128[] WideExponents { get; }
|
||||||
public ulong[] SmallExponents { get; }
|
public ulong[] SmallExponents { get; }
|
||||||
public ulong[] LargeExponents { get; }
|
public ulong[] LargeExponents { get; }
|
||||||
public ulong[] VeryLargeExponents { get; }
|
public ulong[] VeryLargeExponents { get; }
|
||||||
@@ -394,11 +433,13 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
{
|
{
|
||||||
Random random = new(13);
|
Random random = new(13);
|
||||||
Random heuristicRandom = new(113);
|
Random heuristicRandom = new(113);
|
||||||
|
Random wideRandom = new(19);
|
||||||
|
|
||||||
var smallDivisors = new MontgomeryDivisorData[SampleCount];
|
var smallDivisors = new MontgomeryDivisorData[SampleCount];
|
||||||
var largeDivisors = new MontgomeryDivisorData[SampleCount];
|
var largeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||||
var veryLargeDivisors = new MontgomeryDivisorData[SampleCount];
|
var veryLargeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||||
var wideModuli = new UInt128[SampleCount];
|
var wideModuli = new UInt128[SampleCount];
|
||||||
|
var wideExponents = new UInt128[SampleCount];
|
||||||
var smallExponents = new ulong[SampleCount];
|
var smallExponents = new ulong[SampleCount];
|
||||||
var largeExponents = new ulong[SampleCount];
|
var largeExponents = new ulong[SampleCount];
|
||||||
var veryLargeExponents = new ulong[SampleCount];
|
var veryLargeExponents = new ulong[SampleCount];
|
||||||
@@ -427,8 +468,9 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
veryLargeExponents[i] = NextVeryLargeExponent(heuristicRandom);
|
veryLargeExponents[i] = NextVeryLargeExponent(heuristicRandom);
|
||||||
veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus, ref previousPrimeOrder);
|
veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus, ref previousPrimeOrder);
|
||||||
|
|
||||||
UInt128 wideModulus = NextWideOddModulus(heuristicRandom);
|
UInt128 wideModulus = NextWideOddModulus(wideRandom);
|
||||||
wideModuli[i] = wideModulus;
|
wideModuli[i] = wideModulus;
|
||||||
|
wideExponents[i] = NextWideExponent(wideRandom);
|
||||||
wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus, ref previousWidePrimeOrder);
|
wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus, ref previousWidePrimeOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,6 +479,7 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
largeDivisors,
|
largeDivisors,
|
||||||
veryLargeDivisors,
|
veryLargeDivisors,
|
||||||
wideModuli,
|
wideModuli,
|
||||||
|
wideExponents,
|
||||||
smallExponents,
|
smallExponents,
|
||||||
largeExponents,
|
largeExponents,
|
||||||
veryLargeExponents,
|
veryLargeExponents,
|
||||||
@@ -483,10 +526,37 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
{
|
{
|
||||||
Span<byte> buffer = stackalloc byte[16];
|
Span<byte> buffer = stackalloc byte[16];
|
||||||
random.NextBytes(buffer);
|
random.NextBytes(buffer);
|
||||||
|
|
||||||
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
||||||
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]) | (1UL << 63);
|
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]);
|
||||||
UInt128 value = ((UInt128)high << 64) | low;
|
if (high == 0UL)
|
||||||
return value | UInt128.One;
|
{
|
||||||
|
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<byte> 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)
|
private static ulong CalculateCycleLengthWithHeuristics(ulong modulus, ref ulong? previousPrimeOrder)
|
||||||
@@ -851,6 +921,182 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
return (ulong)result;
|
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<GpuUInt128> 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<GpuUInt128> 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)
|
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
|
||||||
{
|
{
|
||||||
UInt128 product = (UInt128)x * y;
|
UInt128 product = (UInt128)x * y;
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
using System.Buffers.Binary;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
|
||||||
|
|
||||||
[MemoryDiagnoser]
|
|
||||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
|
||||||
public class Pow2MontgomeryModUInt128Benchmarks
|
|
||||||
{
|
|
||||||
private const int SampleCount = 256;
|
|
||||||
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
|
||||||
|
|
||||||
private UInt128[] _moduli = Array.Empty<UInt128>();
|
|
||||||
private UInt128[] _exponents = Array.Empty<UInt128>();
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
WideSampleData cache = WideSampleDataCache.Instance;
|
|
||||||
_moduli = cache.Moduli;
|
|
||||||
_exponents = cache.Exponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class WideSampleData
|
|
||||||
{
|
|
||||||
public WideSampleData(UInt128[] moduli, UInt128[] exponents)
|
|
||||||
{
|
|
||||||
Moduli = moduli;
|
|
||||||
Exponents = exponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt128[] Moduli { get; }
|
|
||||||
public UInt128[] Exponents { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class WideSampleDataCache
|
|
||||||
{
|
|
||||||
private static readonly Lazy<WideSampleData> Cache = new(Create);
|
|
||||||
|
|
||||||
public static WideSampleData Instance => Cache.Value;
|
|
||||||
|
|
||||||
private static WideSampleData Create()
|
|
||||||
{
|
|
||||||
Random random = new(19);
|
|
||||||
var moduli = new UInt128[SampleCount];
|
|
||||||
var exponents = new UInt128[SampleCount];
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
moduli[i] = NextWideOddModulus(random);
|
|
||||||
exponents[i] = NextWideExponent(random);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new WideSampleData(moduli, exponents);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UInt128 NextWideOddModulus(Random random)
|
|
||||||
{
|
|
||||||
Span<byte> 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<byte> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Current UInt128-native windowed Montgomery ladder that avoids GpuUInt128 on CPU paths.
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public UInt128 UInt128Windowed()
|
|
||||||
{
|
|
||||||
UInt128 checksum = UInt128.Zero;
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
checksum ^= _exponents[i].Pow2MontgomeryModWindowed(_moduli[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Previous CPU shim that reused the GPU-oriented GpuUInt128 implementation.
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark]
|
|
||||||
public UInt128 GpuStructShim()
|
|
||||||
{
|
|
||||||
UInt128 checksum = UInt128.Zero;
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
checksum ^= Pow2MontgomeryModWindowedGpuShim(_exponents[i], _moduli[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<GpuUInt128> 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<GpuUInt128> oddPowers)
|
|
||||||
{
|
|
||||||
oddPowers[0] = baseValue;
|
|
||||||
if (oddPowers.Length == 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reusing baseValue to hold base^2 for the shared odd-power ladder.
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reusing baseValue to store the squared base for the next iteration.
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,9 @@ public static class Program
|
|||||||
// var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator);
|
// var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator);
|
||||||
// BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
// BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||||
|
|
||||||
Pow2MontgomeryModBenchmarks benchmarks = new()
|
Pow2ModBenchmarks benchmarks = new()
|
||||||
{
|
{
|
||||||
Scale = Pow2MontgomeryModBenchmarks.InputScale.Large
|
Scale = Pow2ModBenchmarks.InputScale.Large
|
||||||
};
|
};
|
||||||
|
|
||||||
GpuPrimeWorkLimiter.SetLimit(1);
|
GpuPrimeWorkLimiter.SetLimit(1);
|
||||||
|
|||||||
Reference in new issue
Block a user