Various optimizations and cleanups. "in" removed from GpuUInt128 to avoid defensive copies from IL.
This commit is contained in:
1 parent
70eca5126b
commit
ca0cadc8a3
18 files changed
+565
-395
No files matched your search
@@ -50,7 +50,7 @@ public class GpuPow2ModBenchmarks
|
|||||||
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, in GpuUInt128 modulus)
|
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
if (modulus.IsZero || modulus == GpuUInt128.One)
|
if (modulus.IsZero || modulus == GpuUInt128.One)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,14 +52,31 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// 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(Baseline = true)]
|
||||||
public ulong Baseline()
|
public ulong BaselineCpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
||||||
|
/// </summary>
|
||||||
|
[Benchmark]
|
||||||
|
public ulong BaselineGpu()
|
||||||
|
{
|
||||||
|
GetData(out var exponents, out var divisors, out _);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -69,14 +86,31 @@ 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 BaselineWithKnownCycle()
|
public ulong BaselineWithKnownCycleCpu()
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||||
|
/// </summary>
|
||||||
|
[Benchmark]
|
||||||
|
public ulong BaselineWithKnownCycleGpu()
|
||||||
|
{
|
||||||
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
|
|||||||
@@ -89,14 +89,32 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// large set.
|
/// large set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark(Baseline = true)]
|
// [Benchmark(Baseline = true)]
|
||||||
public ulong MontgomeryWithoutCycle()
|
public ulong MontgomeryWithoutCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
|
||||||
|
/// large set.
|
||||||
|
/// </summary>
|
||||||
|
// [Benchmark(Baseline = true)]
|
||||||
|
public ulong MontgomeryWithoutCycleGpu()
|
||||||
|
{
|
||||||
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -107,14 +125,32 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// on the large one (0.08×).
|
/// on the large one (0.08×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong MontgomeryWithPrecomputedCycle()
|
public ulong MontgomeryWithPrecomputedCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
|
||||||
|
/// on the large one (0.08×).
|
||||||
|
/// </summary>
|
||||||
|
[Benchmark]
|
||||||
|
public ulong MontgomeryWithPrecomputedCycleGpu()
|
||||||
|
{
|
||||||
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||||
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
|
for (int i = 0; i < SampleCount; i++)
|
||||||
|
{
|
||||||
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -125,7 +161,7 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark]
|
// [Benchmark]
|
||||||
public ulong MontgomeryWithGpuCycleComputation()
|
public ulong MontgomeryWithGpuCycleComputationCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -133,15 +169,33 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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).
|
||||||
|
/// </summary>
|
||||||
|
// [Benchmark]
|
||||||
|
public ulong MontgomeryWithGpuCycleComputationGpu()
|
||||||
|
{
|
||||||
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] 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]
|
[Benchmark]
|
||||||
public ulong MontgomeryWithHeuristicCycleComputation()
|
public ulong MontgomeryWithHeuristicCycleComputationCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -149,7 +203,22 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public ulong MontgomeryWithHeuristicCycleComputationGpu()
|
||||||
|
{
|
||||||
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] 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;
|
return checksum;
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return (UInt128)result;
|
return (UInt128)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldUseSingleBit(in GpuUInt128 exponent)
|
private static bool ShouldUseSingleBit(GpuUInt128 exponent)
|
||||||
{
|
{
|
||||||
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
||||||
{
|
{
|
||||||
@@ -257,7 +257,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||||
{
|
{
|
||||||
if (bitIndex >= 64)
|
if (bitIndex >= 64)
|
||||||
{
|
{
|
||||||
@@ -267,7 +267,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||||
{
|
{
|
||||||
if (windowStart != 0)
|
if (windowStart != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Cpu;
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
||||||
{
|
{
|
||||||
private readonly object _sync = new();
|
private readonly object _sync = new();
|
||||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = [];
|
||||||
private ulong _divisorLimit;
|
private ulong _divisorLimit;
|
||||||
private ulong _lastStatusDivisor;
|
private ulong _lastStatusDivisor;
|
||||||
private bool _isConfigured;
|
private bool _isConfigured;
|
||||||
@@ -213,7 +210,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
ulong mersenne;
|
ulong mersenne;
|
||||||
if (prime <= 63UL)
|
if (prime <= 63UL)
|
||||||
{
|
{
|
||||||
mersenne = (1UL << (int)prime) - 1UL;
|
mersenne = (1UL << (int)prime) - 1UL;
|
||||||
hasExactMersenne = true;
|
hasExactMersenne = true;
|
||||||
}
|
}
|
||||||
else if (prime == 64UL)
|
else if (prime == 64UL)
|
||||||
@@ -249,7 +246,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
||||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||||
}
|
}
|
||||||
@@ -287,12 +284,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
UInt128 incrementAmount = step * increments;
|
UInt128 incrementAmount = step * increments;
|
||||||
divisor += incrementAmount;
|
divisor += incrementAmount;
|
||||||
remainder10 = AdvanceRemainder(remainder10, step10, (byte)10, increments);
|
remainder10 = AdvanceRemainder(remainder10, step10, 10, increments);
|
||||||
remainder8 = AdvanceRemainder(remainder8, step8, (byte)8, increments);
|
remainder8 = AdvanceRemainder(remainder8, step8, 8, increments);
|
||||||
remainder5 = AdvanceRemainder(remainder5, step5, (byte)5, increments);
|
remainder5 = AdvanceRemainder(remainder5, step5, 5, increments);
|
||||||
remainder3 = AdvanceRemainder(remainder3, step3, (byte)3, increments);
|
remainder3 = AdvanceRemainder(remainder3, step3, 3, increments);
|
||||||
remainder7 = AdvanceRemainder(remainder7, step7, (byte)7, increments);
|
remainder7 = AdvanceRemainder(remainder7, step7, 7, increments);
|
||||||
remainder11 = AdvanceRemainder(remainder11, step11, (byte)11, increments);
|
remainder11 = AdvanceRemainder(remainder11, step11, 11, increments);
|
||||||
}
|
}
|
||||||
|
|
||||||
processedAll = divisor > limit;
|
processedAll = divisor > limit;
|
||||||
@@ -301,7 +298,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong residue = prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData);
|
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
|
||||||
return residue == 1UL ? (byte)1 : (byte)0;
|
return residue == 1UL ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,8 +320,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return remainder;
|
return remainder;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong modulusValue = (ulong)modulus;
|
ulong modulusValue = modulus;
|
||||||
ulong scaledDelta = ((ulong)delta * (increments % modulusValue)) % modulusValue;
|
ulong scaledDelta = (delta * (increments % modulusValue)) % modulusValue;
|
||||||
byte reducedDelta = (byte)scaledDelta;
|
byte reducedDelta = (byte)scaledDelta;
|
||||||
int sum = remainder + reducedDelta;
|
int sum = remainder + reducedDelta;
|
||||||
if (sum >= modulus)
|
if (sum >= modulus)
|
||||||
|
|||||||
@@ -1,31 +1,20 @@
|
|||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
internal struct ExponentRemainderStepper
|
internal struct ExponentRemainderStepper(in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
private readonly MontgomeryDivisorData _divisor;
|
private readonly MontgomeryDivisorData _divisor = divisor;
|
||||||
private readonly ulong _modulus;
|
private readonly ulong _modulus = divisor.Modulus;
|
||||||
private readonly ulong _nPrime;
|
private readonly ulong _nPrime = divisor.NPrime;
|
||||||
private readonly ulong _montgomeryOne;
|
private readonly ulong _montgomeryOne = divisor.MontgomeryOne;
|
||||||
private ulong _previousExponent;
|
private ulong _previousExponent = 0UL;
|
||||||
private ulong _currentMontgomery;
|
private ulong _currentMontgomery = divisor.MontgomeryOne;
|
||||||
private bool _hasState;
|
private bool _hasState = false;
|
||||||
|
|
||||||
public ExponentRemainderStepper(in MontgomeryDivisorData divisor)
|
public readonly bool IsValidModulus => _modulus > 1UL && (_modulus & 1UL) != 0UL;
|
||||||
{
|
|
||||||
_divisor = divisor;
|
|
||||||
_modulus = divisor.Modulus;
|
|
||||||
_nPrime = divisor.NPrime;
|
|
||||||
_montgomeryOne = divisor.MontgomeryOne;
|
|
||||||
_previousExponent = 0UL;
|
|
||||||
_currentMontgomery = divisor.MontgomeryOne;
|
|
||||||
_hasState = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsValidModulus => _modulus > 1UL && (_modulus & 1UL) != 0UL;
|
public readonly bool HasState => _hasState;
|
||||||
|
|
||||||
public bool HasState => _hasState;
|
public readonly ulong PreviousExponent => _previousExponent;
|
||||||
|
|
||||||
public ulong PreviousExponent => _previousExponent;
|
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
@@ -47,7 +36,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
||||||
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
|
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
|
||||||
// 2× gains for large exponents.
|
// 2× gains for large exponents.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
return ReduceCurrent();
|
return ReduceCurrent();
|
||||||
@@ -59,7 +48,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
||||||
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
||||||
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
|
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
|
||||||
ulong multiplier = delta.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
return ReduceCurrent();
|
return ReduceCurrent();
|
||||||
@@ -77,7 +66,7 @@ internal struct ExponentRemainderStepper
|
|||||||
{
|
{
|
||||||
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
||||||
// residue checks align with the optimized ProcessEightBitWindows timings.
|
// residue checks align with the optimized ProcessEightBitWindows timings.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
return _currentMontgomery == _montgomeryOne;
|
return _currentMontgomery == _montgomeryOne;
|
||||||
@@ -88,7 +77,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
||||||
// TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
// TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
||||||
// currently lags behind the benchmarked implementation for huge divisor cycles.
|
// currently lags behind the benchmarked implementation for huge divisor cycles.
|
||||||
ulong multiplier = delta.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
return _currentMontgomery == _montgomeryOne;
|
return _currentMontgomery == _montgomeryOne;
|
||||||
@@ -123,7 +112,7 @@ internal struct ExponentRemainderStepper
|
|||||||
{
|
{
|
||||||
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
||||||
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
|
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
isUnity = _currentMontgomery == _montgomeryOne;
|
isUnity = _currentMontgomery == _montgomeryOne;
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
public readonly struct FactorEntry(ulong value, int exponent)
|
||||||
|
{
|
||||||
|
public readonly ulong Value = value;
|
||||||
|
public readonly int Exponent = exponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -267,7 +267,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Add(in GpuUInt128 other) => AddInternal(other.High, other.Low);
|
public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void AddInternal(ulong otherHigh, ulong otherLow)
|
private void AddInternal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -294,17 +294,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
public void AddMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
public void AddMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
public void AddMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
@@ -390,7 +390,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
public void AddMod(GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(ulong value, ulong modulus)
|
public void AddMod(ulong value, ulong modulus)
|
||||||
@@ -414,7 +414,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Sub(in GpuUInt128 other) => SubInternal(other.High, other.Low);
|
public void Sub(GpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void SubInternal(ulong otherHigh, ulong otherLow)
|
private void SubInternal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -461,7 +461,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Mul64(in GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
public void Mul64(GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -588,7 +588,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int TrailingZeroCount(in GpuUInt128 value)
|
public static int TrailingZeroCount(GpuUInt128 value)
|
||||||
{
|
{
|
||||||
if (value.Low != 0UL)
|
if (value.Low != 0UL)
|
||||||
{
|
{
|
||||||
@@ -646,17 +646,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
public void SubMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
public void SubMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
public void SubMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
@@ -727,7 +727,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Mul(in GpuUInt128 other)
|
public void Mul(GpuUInt128 other)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
||||||
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
hits[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
hits[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
@@ -770,7 +770,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
|
|
||||||
results[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: true);
|
results[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
|
|||||||
@@ -811,7 +811,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong reduced = phi / factor;
|
ulong reduced = phi / factor;
|
||||||
if (reduced.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL)
|
if (reduced.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1595,7 +1595,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
|
|
||||||
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong CalculateByDoublingKernel(ulong prime)
|
private static ulong CalculateByDoublingKernel(ulong prime)
|
||||||
@@ -1795,7 +1795,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false);
|
return exponent.Pow2MontgomeryModWindowedGpu(divisorData, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
||||||
@@ -1811,7 +1811,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
|
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
|
||||||
{
|
{
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
remainders[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false);
|
remainders[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
internal static class HeuristicFailureLog
|
||||||
|
{
|
||||||
|
private const string LogFileName = "prime-order-heuristic-fallbacks.log";
|
||||||
|
private const int FlushThreshold = 4096;
|
||||||
|
private const int MaxBufferedEntries = 16;
|
||||||
|
private static readonly ThreadLocal<StringBuilder?> s_threadBuilder = new(() => StringBuilderPool.Rent(), trackAllValues: true);
|
||||||
|
private static readonly ThreadLocal<int> s_threadEntryCount = new(() => 0, trackAllValues: true);
|
||||||
|
private static readonly object s_fileLock = new();
|
||||||
|
private static readonly string s_logPath = Path.Combine(AppContext.BaseDirectory, LogFileName);
|
||||||
|
private static readonly UTF8Encoding s_utf8NoBom = new(false);
|
||||||
|
|
||||||
|
static HeuristicFailureLog()
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.ProcessExit += static (_, _) => FlushAll(force: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Record(ulong prime, ulong? candidateOrder, HeuristicFailureReason reason)
|
||||||
|
{
|
||||||
|
string primeText = prime.ToString(CultureInfo.InvariantCulture);
|
||||||
|
string? candidateText = candidateOrder.HasValue ? candidateOrder.Value.ToString(CultureInfo.InvariantCulture) : null;
|
||||||
|
RecordInternal(primeText, candidateText, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Record(UInt128 prime, UInt128? candidateOrder, HeuristicFailureReason reason)
|
||||||
|
{
|
||||||
|
string primeText = prime.ToString(CultureInfo.InvariantCulture);
|
||||||
|
string? candidateText = candidateOrder.HasValue ? candidateOrder.Value.ToString(CultureInfo.InvariantCulture) : null;
|
||||||
|
RecordInternal(primeText, candidateText, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RecordInternal(string primeText, string? candidateText, HeuristicFailureReason reason)
|
||||||
|
{
|
||||||
|
StringBuilder? existing = s_threadBuilder.Value;
|
||||||
|
StringBuilder builder = existing ?? StringBuilderPool.Rent();
|
||||||
|
int count = s_threadEntryCount.Value + 1;
|
||||||
|
bool forceFlush = count >= MaxBufferedEntries;
|
||||||
|
builder.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
|
||||||
|
builder.Append(" | prime=");
|
||||||
|
builder.Append(primeText);
|
||||||
|
builder.Append(" | reason=");
|
||||||
|
builder.Append(reason);
|
||||||
|
if (candidateText is not null)
|
||||||
|
{
|
||||||
|
builder.Append(" | candidateOrder=");
|
||||||
|
builder.Append(candidateText);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine();
|
||||||
|
FlushBuilderIfNeeded(builder, forceFlush);
|
||||||
|
if (builder.Length == 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_threadEntryCount.Value = count;
|
||||||
|
s_threadBuilder.Value = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FlushAll(bool force)
|
||||||
|
{
|
||||||
|
foreach (StringBuilder? builder in s_threadBuilder.Values)
|
||||||
|
{
|
||||||
|
if (builder is not null)
|
||||||
|
{
|
||||||
|
FlushBuilderIfNeeded(builder, force);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FlushBuilderIfNeeded(StringBuilder builder, bool force)
|
||||||
|
{
|
||||||
|
if (builder.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!force && builder.Length < FlushThreshold)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string text = builder.ToString();
|
||||||
|
builder.Clear();
|
||||||
|
lock (s_fileLock)
|
||||||
|
{
|
||||||
|
using FileStream stream = new FileStream(s_logPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||||
|
using StreamWriter writer = new StreamWriter(stream, s_utf8NoBom);
|
||||||
|
writer.Write(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
internal enum HeuristicFailureReason
|
||||||
|
{
|
||||||
|
PhiPartialFactorizationFailed,
|
||||||
|
StrictModeRequested,
|
||||||
|
HeuristicPipelineUnresolved,
|
||||||
|
HeuristicPipelineAssumedFullOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -54,15 +54,15 @@ public class MersenneDivisorCycles
|
|||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CycleEqualsExponent(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
public static bool CycleEqualsExponentCpu(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||||
{
|
{
|
||||||
Dictionary<ulong, FactorCacheEntry>? cache = null;
|
Dictionary<ulong, FactorCacheEntry>? cache = null;
|
||||||
return CycleEqualsExponent(divisor, exponent, divisorData, ref cache);
|
return CycleEqualsExponentCpu(divisor, exponent, divisorData, ref cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CycleEqualsExponent(ulong divisor, ulong exponent, in MontgomeryDivisorData divisorData, ref Dictionary<ulong, FactorCacheEntry>? factorCache)
|
public static bool CycleEqualsExponentCpu(ulong divisor, ulong exponent, in MontgomeryDivisorData divisorData, ref Dictionary<ulong, FactorCacheEntry>? factorCache)
|
||||||
{
|
{
|
||||||
if (CycleEqualsExponentForMersenneCandidate(divisor, divisorData, exponent))
|
if (CycleEqualsExponentForMersenneCandidateCpu(divisor, divisorData, exponent))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -97,15 +97,15 @@ public class MersenneDivisorCycles
|
|||||||
factorCache = cache;
|
factorCache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryCalculateCycleLengthForExponent(divisor, exponent, divisorData, cache, out ulong cycleLength) && cycleLength != 0UL)
|
if (TryCalculateCycleLengthForExponentCpu(divisor, exponent, divisorData, cache, out ulong cycleLength) && cycleLength != 0UL)
|
||||||
{
|
{
|
||||||
return cycleLength == exponent;
|
return cycleLength == exponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
public static bool CycleEqualsExponentForMersenneCandidateCpu(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||||
{
|
{
|
||||||
if (!IsValidMersenneDivisorCandidate(divisor, exponent))
|
if (!IsValidMersenneDivisorCandidate(divisor, exponent))
|
||||||
{
|
{
|
||||||
@@ -126,7 +126,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
||||||
@@ -264,7 +264,7 @@ public class MersenneDivisorCycles
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryCalculateCycleLengthForExponent(
|
public static bool TryCalculateCycleLengthForExponentCpu(
|
||||||
ulong divisor,
|
ulong divisor,
|
||||||
ulong exponent,
|
ulong exponent,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
@@ -327,7 +327,7 @@ public class MersenneDivisorCycles
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
cycleLength = ReduceOrderCpu(divisorData, phi, factorCounts);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
|
private static ulong ReduceOrderCpu(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
|
||||||
{
|
{
|
||||||
if (factorCounts.Count == 0)
|
if (factorCounts.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -505,7 +505,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong candidate = order / prime;
|
ulong candidate = order / prime;
|
||||||
if (candidate.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL)
|
if (candidate.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL)
|
||||||
{
|
{
|
||||||
order = candidate;
|
order = candidate;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
public sealed class PartialFactorResult
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static PartialFactorResult? s_poolHead;
|
||||||
|
|
||||||
|
public FactorEntry[]? Factors;
|
||||||
|
private PartialFactorResult? _next;
|
||||||
|
public ulong Cofactor;
|
||||||
|
public bool FullyFactored;
|
||||||
|
public int Count;
|
||||||
|
|
||||||
|
private PartialFactorResult()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartialFactorResult Rent(in FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count)
|
||||||
|
{
|
||||||
|
PartialFactorResult? instance = s_poolHead;
|
||||||
|
if (instance is null)
|
||||||
|
{
|
||||||
|
instance = new();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_poolHead = instance._next;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance._next = null;
|
||||||
|
instance.Factors = factors;
|
||||||
|
instance.Cofactor = cofactor;
|
||||||
|
instance.FullyFactored = fullyFactored;
|
||||||
|
instance.Count = count;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly PartialFactorResult Empty = new() { Cofactor = 1UL, FullyFactored = true };
|
||||||
|
|
||||||
|
public PartialFactorResult WithAdditionalPrime(ulong prime)
|
||||||
|
{
|
||||||
|
FactorEntry[]? source = Factors;
|
||||||
|
if (source is null || Count == 0)
|
||||||
|
{
|
||||||
|
FactorEntry[] local = ArrayPool<FactorEntry>.Shared.Rent(1);
|
||||||
|
local[0] = new FactorEntry(prime, 1);
|
||||||
|
return Rent(local, 1UL, true, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
FactorEntry[] extended = ArrayPool<FactorEntry>.Shared.Rent(Count + 1);
|
||||||
|
Array.Copy(source, 0, extended, 0, Count);
|
||||||
|
extended[Count] = new FactorEntry(prime, 1);
|
||||||
|
Span<FactorEntry> span = extended.AsSpan(0, Count + 1);
|
||||||
|
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
|
return Rent(extended, 1UL, true, Count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this != Empty)
|
||||||
|
{
|
||||||
|
FactorEntry[]? factors = Factors;
|
||||||
|
if (factors is not null)
|
||||||
|
{
|
||||||
|
Factors = null;
|
||||||
|
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = s_poolHead;
|
||||||
|
s_poolHead = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ public static class PerfectNumberConstants
|
|||||||
public const int MaxQForDivisorCycles = 4_000_000;
|
public const int MaxQForDivisorCycles = 4_000_000;
|
||||||
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
||||||
public const int MaxOddPowersCount = 128;
|
public const int MaxOddPowersCount = 128;
|
||||||
|
public const int PooledArrayThreshold = 64;
|
||||||
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
||||||
// divisor-cycle datasets we generate offline.
|
// divisor-cycle datasets we generate offline.
|
||||||
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
||||||
|
|||||||
@@ -1,60 +1,31 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
internal static partial class PrimeOrderCalculator
|
internal static partial class PrimeOrderCalculator
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
internal enum PrimeOrderMode
|
internal enum PrimeOrderMode
|
||||||
{
|
{
|
||||||
Heuristic,
|
Heuristic,
|
||||||
Strict,
|
Strict,
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum HeuristicFailureReason
|
internal readonly struct PrimeOrderSearchConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, PrimeOrderMode mode)
|
||||||
{
|
{
|
||||||
PhiPartialFactorizationFailed,
|
public static PrimeOrderSearchConfig HeuristicDefault => new(smallFactorLimit: 100_000, pollardRhoMilliseconds: 128, maxPowChecks: 24, PrimeOrderMode.Heuristic);
|
||||||
StrictModeRequested,
|
|
||||||
HeuristicPipelineUnresolved,
|
|
||||||
HeuristicPipelineAssumedFullOrder,
|
|
||||||
}
|
|
||||||
|
|
||||||
internal readonly struct PrimeOrderSearchConfig
|
|
||||||
{
|
|
||||||
public PrimeOrderSearchConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, PrimeOrderMode mode)
|
|
||||||
{
|
|
||||||
SmallFactorLimit = smallFactorLimit;
|
|
||||||
PollardRhoMilliseconds = pollardRhoMilliseconds;
|
|
||||||
MaxPowChecks = maxPowChecks;
|
|
||||||
Mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public uint SmallFactorLimit { get; }
|
|
||||||
|
|
||||||
public int PollardRhoMilliseconds { get; }
|
|
||||||
|
|
||||||
public int MaxPowChecks { get; }
|
|
||||||
|
|
||||||
public PrimeOrderMode Mode { get; }
|
|
||||||
|
|
||||||
public static PrimeOrderSearchConfig HeuristicDefault => new(smallFactorLimit: 100_000, pollardRhoMilliseconds: 128, maxPowChecks: 24, PrimeOrderMode.Heuristic);
|
|
||||||
|
|
||||||
public static PrimeOrderSearchConfig StrictDefault => new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, PrimeOrderMode.Strict);
|
public static PrimeOrderSearchConfig StrictDefault => new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, PrimeOrderMode.Strict);
|
||||||
|
|
||||||
|
public readonly uint SmallFactorLimit = smallFactorLimit;
|
||||||
|
public readonly int PollardRhoMilliseconds = pollardRhoMilliseconds;
|
||||||
|
public readonly int MaxPowChecks = maxPowChecks;
|
||||||
|
public readonly PrimeOrderMode Mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum PrimeOrderHeuristicDevice
|
internal enum PrimeOrderHeuristicDevice
|
||||||
@@ -88,29 +59,30 @@ internal static partial class PrimeOrderCalculator
|
|||||||
private static Dictionary<ulong, int>? s_partialFactorCounts;
|
private static Dictionary<ulong, int>? s_partialFactorCounts;
|
||||||
|
|
||||||
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
|
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
|
||||||
private readonly struct Pow2ModeScope
|
|
||||||
{
|
|
||||||
private readonly bool _previousInitialized;
|
|
||||||
private readonly bool _previousAllow;
|
|
||||||
private readonly PrimeOrderHeuristicDevice _previousDevice;
|
|
||||||
|
|
||||||
public Pow2ModeScope(PrimeOrderHeuristicDevice device)
|
private readonly struct Pow2ModeScope
|
||||||
{
|
{
|
||||||
_previousInitialized = s_pow2ModeInitialized;
|
private readonly bool _previousInitialized;
|
||||||
_previousAllow = s_allowGpuPow2;
|
private readonly bool _previousAllow;
|
||||||
_previousDevice = s_deviceMode;
|
private readonly PrimeOrderHeuristicDevice _previousDevice;
|
||||||
s_pow2ModeInitialized = true;
|
|
||||||
s_allowGpuPow2 = device == PrimeOrderHeuristicDevice.Gpu;
|
|
||||||
s_deviceMode = device;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public Pow2ModeScope(PrimeOrderHeuristicDevice device)
|
||||||
{
|
{
|
||||||
s_allowGpuPow2 = _previousAllow;
|
_previousInitialized = s_pow2ModeInitialized;
|
||||||
s_pow2ModeInitialized = _previousInitialized;
|
_previousAllow = s_allowGpuPow2;
|
||||||
s_deviceMode = _previousDevice;
|
_previousDevice = s_deviceMode;
|
||||||
}
|
s_pow2ModeInitialized = true;
|
||||||
}
|
s_allowGpuPow2 = device == PrimeOrderHeuristicDevice.Gpu;
|
||||||
|
s_deviceMode = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
s_allowGpuPow2 = _previousAllow;
|
||||||
|
s_pow2ModeInitialized = _previousInitialized;
|
||||||
|
s_deviceMode = _previousDevice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly struct DebugLoggingScope
|
private readonly struct DebugLoggingScope
|
||||||
{
|
{
|
||||||
@@ -184,17 +156,22 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return gpuOrder;
|
return gpuOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
using PartialFactorResult phiFactors = PartialFactor(phi, config);
|
PartialFactorResult phiFactors = PartialFactor(phi, config);
|
||||||
|
|
||||||
if (phiFactors.Factors is null)
|
ulong result;
|
||||||
{
|
if (phiFactors.Factors is null)
|
||||||
return FinishStrictly(prime, divisorData);
|
{
|
||||||
}
|
result = FinishStrictlyCpu(prime, divisorData);
|
||||||
|
phiFactors.Dispose();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
return RunHeuristicPipeline(prime, previousOrder, config, divisorData, phi, phiFactors);
|
result = RunHeuristicPipelineCpu(prime, previousOrder, config, divisorData, phi, phiFactors);
|
||||||
|
phiFactors.Dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong RunHeuristicPipeline(
|
private static ulong RunHeuristicPipelineCpu(
|
||||||
ulong prime,
|
ulong prime,
|
||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
in PrimeOrderSearchConfig config,
|
in PrimeOrderSearchConfig config,
|
||||||
@@ -202,25 +179,25 @@ internal static partial class PrimeOrderCalculator
|
|||||||
ulong phi,
|
ulong phi,
|
||||||
PartialFactorResult phiFactors)
|
PartialFactorResult phiFactors)
|
||||||
{
|
{
|
||||||
if (phiFactors.FullyFactored && TrySpecialMax(phi, prime, phiFactors, divisorData))
|
if (phiFactors.FullyFactored && TrySpecialMaxCpu(phi, prime, phiFactors, divisorData))
|
||||||
{
|
{
|
||||||
return phi;
|
return phi;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong candidateOrder = InitializeStartingOrder(prime, phi, divisorData);
|
ulong candidateOrder = InitializeStartingOrderCpu(prime, phi, divisorData);
|
||||||
candidateOrder = ExponentLowering(candidateOrder, prime, phiFactors, divisorData);
|
candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData);
|
||||||
|
|
||||||
if (TryConfirmOrder(prime, candidateOrder, divisorData, config))
|
if (TryConfirmOrderCpu(prime, candidateOrder, divisorData, config))
|
||||||
{
|
{
|
||||||
return candidateOrder;
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.Mode == PrimeOrderMode.Strict)
|
if (config.Mode == PrimeOrderMode.Strict)
|
||||||
{
|
{
|
||||||
return FinishStrictly(prime, divisorData);
|
return FinishStrictlyCpu(prime, divisorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryHeuristicFinish(prime, candidateOrder, previousOrder, divisorData, config, phiFactors, out ulong order))
|
if (TryHeuristicFinishCpu(prime, candidateOrder, previousOrder, divisorData, config, phiFactors, out ulong order))
|
||||||
{
|
{
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
@@ -228,12 +205,12 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return candidateOrder;
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong FinishStrictly(ulong prime, in MontgomeryDivisorData divisorData)
|
private static ulong FinishStrictlyCpu(ulong prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
return CalculateByFactorization(prime, divisorData);
|
return CalculateByFactorizationCpu(prime, divisorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TrySpecialMax(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<FactorEntry> factorSpan = factors.Factors;
|
ReadOnlySpan<FactorEntry> factorSpan = factors.Factors;
|
||||||
int length = factors.Count;
|
int length = factors.Count;
|
||||||
@@ -241,7 +218,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
ulong factor = factorSpan[i].Value;
|
ulong factor = factorSpan[i].Value;
|
||||||
ulong reduced = phi / factor;
|
ulong reduced = phi / factor;
|
||||||
if (Pow2EqualsOne(reduced, prime, divisorData))
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -250,13 +227,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong InitializeStartingOrder(ulong prime, ulong phi, in MontgomeryDivisorData divisorData)
|
private static ulong InitializeStartingOrderCpu(ulong prime, ulong phi, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong order = phi;
|
ulong order = phi;
|
||||||
if ((prime & 7UL) == 1UL || (prime & 7UL) == 7UL)
|
if ((prime & 7UL) == 1UL || (prime & 7UL) == 7UL)
|
||||||
{
|
{
|
||||||
ulong half = phi >> 1;
|
ulong half = phi >> 1;
|
||||||
if (Pow2EqualsOne(half, prime, divisorData))
|
if (Pow2EqualsOneCpu(half, prime, divisorData))
|
||||||
{
|
{
|
||||||
order = half;
|
order = half;
|
||||||
}
|
}
|
||||||
@@ -265,7 +242,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ExponentLowering(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
FactorEntry[]? tempArray = null;
|
FactorEntry[]? tempArray = null;
|
||||||
try
|
try
|
||||||
@@ -298,7 +275,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong reduced = order / primeFactor;
|
ulong reduced = order / primeFactor;
|
||||||
if (Pow2EqualsOne(reduced, prime, divisorData))
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
{
|
{
|
||||||
order = reduced;
|
order = reduced;
|
||||||
continue;
|
continue;
|
||||||
@@ -319,7 +296,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConfirmOrder(ulong prime, ulong order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
private static bool TryConfirmOrderCpu(ulong prime, ulong order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (order == 0UL)
|
if (order == 0UL)
|
||||||
{
|
{
|
||||||
@@ -328,7 +305,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
// Calculating `a^order ≡ 1 (mod p)` is a prerequisite for `order` being the actual order of 2 modulo `p`.
|
// Calculating `a^order ≡ 1 (mod p)` is a prerequisite for `order` being the actual order of 2 modulo `p`.
|
||||||
// DebugLog("Verifying a^order ≡ 1 (mod p)");
|
// DebugLog("Verifying a^order ≡ 1 (mod p)");
|
||||||
if (!Pow2EqualsOne(order, prime, divisorData))
|
if (!Pow2EqualsOneCpu(order, prime, divisorData))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -380,7 +357,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduced /= primeFactor;
|
reduced /= primeFactor;
|
||||||
if (Pow2EqualsOne(reduced, prime, divisorData))
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -395,7 +372,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryHeuristicFinish(
|
private static bool TryHeuristicFinishCpu(
|
||||||
ulong prime,
|
ulong prime,
|
||||||
ulong order,
|
ulong order,
|
||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
@@ -528,14 +505,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
equalsOne = Pow2EqualsOne(candidate, prime, divisorData);
|
equalsOne = Pow2EqualsOneCpu(candidate, prime, divisorData);
|
||||||
}
|
}
|
||||||
if (!equalsOne)
|
if (!equalsOne)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConfirmCandidate(prime, candidate, divisorData, config, ref powUsed, powBudget))
|
if (!TryConfirmCandidateCpu(prime, candidate, divisorData, config, ref powUsed, powBudget))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -733,7 +710,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConfirmCandidate(ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget)
|
private static bool TryConfirmCandidateCpu(ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget)
|
||||||
{
|
{
|
||||||
PartialFactorResult factorization = PartialFactor(candidate, config);
|
PartialFactorResult factorization = PartialFactor(candidate, config);
|
||||||
try
|
try
|
||||||
@@ -782,7 +759,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
powUsed++;
|
powUsed++;
|
||||||
if (Pow2EqualsOne(reduced, prime, divisorData))
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -797,7 +774,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool Pow2EqualsOne(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData)
|
private static bool Pow2EqualsOneCpu(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
if (IsGpuPow2Allowed)
|
if (IsGpuPow2Allowed)
|
||||||
{
|
{
|
||||||
@@ -808,111 +785,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
|
||||||
|
|
||||||
private static class HeuristicFailureLog
|
|
||||||
{
|
|
||||||
private const string LogFileName = "prime-order-heuristic-fallbacks.log";
|
|
||||||
private const int FlushThreshold = 4096;
|
|
||||||
private const int MaxBufferedEntries = 16;
|
|
||||||
private static readonly ConcurrentQueue<StringBuilder> s_builderPool = new();
|
|
||||||
private static readonly ThreadLocal<StringBuilder?> s_threadBuilder = new(() => AcquireBuilder(), trackAllValues: true);
|
|
||||||
private static readonly ThreadLocal<int> s_threadEntryCount = new(() => 0, trackAllValues: true);
|
|
||||||
private static readonly object s_fileLock = new();
|
|
||||||
private static readonly string s_logPath = Path.Combine(AppContext.BaseDirectory, LogFileName);
|
|
||||||
private static readonly UTF8Encoding s_utf8NoBom = new(false);
|
|
||||||
|
|
||||||
static HeuristicFailureLog()
|
|
||||||
{
|
|
||||||
AppDomain.CurrentDomain.ProcessExit += static (_, _) => FlushAll(force: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Record(ulong prime, ulong? candidateOrder, HeuristicFailureReason reason)
|
|
||||||
{
|
|
||||||
string primeText = prime.ToString(CultureInfo.InvariantCulture);
|
|
||||||
string? candidateText = candidateOrder.HasValue ? candidateOrder.Value.ToString(CultureInfo.InvariantCulture) : null;
|
|
||||||
RecordInternal(primeText, candidateText, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Record(UInt128 prime, UInt128? candidateOrder, HeuristicFailureReason reason)
|
|
||||||
{
|
|
||||||
string primeText = prime.ToString(CultureInfo.InvariantCulture);
|
|
||||||
string? candidateText = candidateOrder.HasValue ? candidateOrder.Value.ToString(CultureInfo.InvariantCulture) : null;
|
|
||||||
RecordInternal(primeText, candidateText, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RecordInternal(string primeText, string? candidateText, HeuristicFailureReason reason)
|
|
||||||
{
|
|
||||||
StringBuilder? existing = s_threadBuilder.Value;
|
|
||||||
StringBuilder builder = existing ?? AcquireBuilder();
|
|
||||||
int count = s_threadEntryCount.Value + 1;
|
|
||||||
bool forceFlush = count >= MaxBufferedEntries;
|
|
||||||
builder.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
|
|
||||||
builder.Append(" | prime=");
|
|
||||||
builder.Append(primeText);
|
|
||||||
builder.Append(" | reason=");
|
|
||||||
builder.Append(reason);
|
|
||||||
if (candidateText is not null)
|
|
||||||
{
|
|
||||||
builder.Append(" | candidateOrder=");
|
|
||||||
builder.Append(candidateText);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.AppendLine();
|
|
||||||
FlushBuilderIfNeeded(builder, forceFlush);
|
|
||||||
if (builder.Length == 0)
|
|
||||||
{
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
s_threadEntryCount.Value = count;
|
|
||||||
s_threadBuilder.Value = builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void FlushAll(bool force)
|
|
||||||
{
|
|
||||||
foreach (StringBuilder? builder in s_threadBuilder.Values)
|
|
||||||
{
|
|
||||||
if (builder is not null)
|
|
||||||
{
|
|
||||||
FlushBuilderIfNeeded(builder, force);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void FlushBuilderIfNeeded(StringBuilder builder, bool force)
|
|
||||||
{
|
|
||||||
if (builder.Length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!force && builder.Length < FlushThreshold)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string text = builder.ToString();
|
|
||||||
builder.Clear();
|
|
||||||
lock (s_fileLock)
|
|
||||||
{
|
|
||||||
using FileStream stream = new FileStream(s_logPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
|
||||||
using StreamWriter writer = new StreamWriter(stream, s_utf8NoBom);
|
|
||||||
writer.Write(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StringBuilder AcquireBuilder()
|
|
||||||
{
|
|
||||||
if (s_builderPool.TryDequeue(out StringBuilder? builder))
|
|
||||||
{
|
|
||||||
builder.Clear();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new StringBuilder(FlushThreshold);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ulong> AcquirePendingCompositeList(int capacityHint)
|
private static List<ulong> AcquirePendingCompositeList(int capacityHint)
|
||||||
@@ -952,16 +825,15 @@ internal static partial class PrimeOrderCalculator
|
|||||||
private static Dictionary<ulong, int> AcquireFactorCountDictionary(int capacityHint)
|
private static Dictionary<ulong, int> AcquireFactorCountDictionary(int capacityHint)
|
||||||
{
|
{
|
||||||
Dictionary<ulong, int>? dictionary = s_partialFactorCounts;
|
Dictionary<ulong, int>? dictionary = s_partialFactorCounts;
|
||||||
int adjustedCapacity = Math.Max(capacityHint, 8);
|
|
||||||
if (dictionary is null)
|
if (dictionary is null)
|
||||||
{
|
{
|
||||||
dictionary = new Dictionary<ulong, int>(adjustedCapacity);
|
dictionary = new Dictionary<ulong, int>(capacityHint);
|
||||||
s_partialFactorCounts = dictionary;
|
s_partialFactorCounts = dictionary;
|
||||||
return dictionary;
|
return dictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
dictionary.Clear();
|
dictionary.Clear();
|
||||||
dictionary.EnsureCapacity(adjustedCapacity);
|
dictionary.EnsureCapacity(capacityHint);
|
||||||
return dictionary;
|
return dictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,7 +841,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (value <= 1UL)
|
if (value <= 1UL)
|
||||||
{
|
{
|
||||||
return PartialFactorResult.RentEmpty();
|
return PartialFactorResult.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int FactorSlotCount = GpuSmallPrimeFactorSlots;
|
const int FactorSlotCount = GpuSmallPrimeFactorSlots;
|
||||||
@@ -1459,7 +1331,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong CalculateByFactorization(ulong prime, in MontgomeryDivisorData divisorData)
|
private static ulong CalculateByFactorizationCpu(ulong prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong phi = prime - 1UL;
|
ulong phi = prime - 1UL;
|
||||||
Dictionary<ulong, int> counts = new(capacity: 8);
|
Dictionary<ulong, int> counts = new(capacity: 8);
|
||||||
@@ -1486,7 +1358,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong candidate = order / primeFactor;
|
ulong candidate = order / primeFactor;
|
||||||
if (Pow2EqualsOne(candidate, prime, divisorData))
|
if (Pow2EqualsOneCpu(candidate, prime, divisorData))
|
||||||
{
|
{
|
||||||
order = candidate;
|
order = candidate;
|
||||||
continue;
|
continue;
|
||||||
@@ -1557,96 +1429,4 @@ internal static partial class PrimeOrderCalculator
|
|||||||
public readonly long Secondary = secondary;
|
public readonly long Secondary = secondary;
|
||||||
public readonly long Tertiary = tertiary;
|
public readonly long Tertiary = tertiary;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct FactorEntry(ulong value, int exponent)
|
|
||||||
{
|
|
||||||
public readonly ulong Value = value;
|
|
||||||
public readonly int Exponent = exponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class PartialFactorResult : IDisposable
|
|
||||||
{
|
|
||||||
[ThreadStatic]
|
|
||||||
private static PartialFactorResult? s_poolHead;
|
|
||||||
|
|
||||||
private PartialFactorResult? _next;
|
|
||||||
private bool _returned;
|
|
||||||
|
|
||||||
public FactorEntry[]? Factors;
|
|
||||||
public ulong Cofactor;
|
|
||||||
public bool FullyFactored;
|
|
||||||
public int Count;
|
|
||||||
|
|
||||||
private PartialFactorResult()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PartialFactorResult Rent(FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count)
|
|
||||||
{
|
|
||||||
PartialFactorResult? instance = s_poolHead;
|
|
||||||
if (instance is null)
|
|
||||||
{
|
|
||||||
instance = new PartialFactorResult();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s_poolHead = instance._next;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance._next = null;
|
|
||||||
instance._returned = false;
|
|
||||||
instance.Factors = factors;
|
|
||||||
instance.Cofactor = cofactor;
|
|
||||||
instance.FullyFactored = fullyFactored;
|
|
||||||
instance.Count = count;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PartialFactorResult RentEmpty()
|
|
||||||
{
|
|
||||||
return Rent(null, 1UL, true, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartialFactorResult WithAdditionalPrime(ulong prime)
|
|
||||||
{
|
|
||||||
FactorEntry[]? source = Factors;
|
|
||||||
if (source is null || Count == 0)
|
|
||||||
{
|
|
||||||
FactorEntry[] local = ArrayPool<FactorEntry>.Shared.Rent(1);
|
|
||||||
local[0] = new FactorEntry(prime, 1);
|
|
||||||
return Rent(local, 1UL, true, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
FactorEntry[] extended = ArrayPool<FactorEntry>.Shared.Rent(Count + 1);
|
|
||||||
Array.Copy(source, 0, extended, 0, Count);
|
|
||||||
extended[Count] = new FactorEntry(prime, 1);
|
|
||||||
Span<FactorEntry> span = extended.AsSpan(0, Count + 1);
|
|
||||||
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
|
||||||
return Rent(extended, 1UL, true, Count + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_returned)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_returned = true;
|
|
||||||
|
|
||||||
FactorEntry[]? factors = Factors;
|
|
||||||
if (factors is not null)
|
|
||||||
{
|
|
||||||
Factors = null;
|
|
||||||
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cofactor = 0UL;
|
|
||||||
FullyFactored = false;
|
|
||||||
Count = 0;
|
|
||||||
|
|
||||||
_next = s_poolHead;
|
|
||||||
s_poolHead = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ public static class UInt128Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||||
{
|
{
|
||||||
if (bitIndex >= 64)
|
if (bitIndex >= 64)
|
||||||
{
|
{
|
||||||
@@ -229,18 +229,16 @@ public static class UInt128Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||||
{
|
{
|
||||||
|
ulong mask = (1UL << windowBitCount) - 1UL;
|
||||||
if (windowStart != 0)
|
if (windowStart != 0)
|
||||||
{
|
{
|
||||||
GpuUInt128 shifted = exponent;
|
exponent.ShiftRight(windowStart);
|
||||||
shifted.ShiftRight(windowStart);
|
return exponent.Low & mask;
|
||||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
|
||||||
return shifted.Low & mask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong directMask = (1UL << windowBitCount) - 1UL;
|
return exponent.Low & mask;
|
||||||
return exponent.Low & directMask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using ILGPU.Algorithms;
|
using ILGPU.Algorithms;
|
||||||
@@ -76,7 +77,7 @@ public static class ULongExtensions
|
|||||||
public static ulong MultiplyShiftRightShiftFirst(ulong value, ulong multiplier, int shift)
|
public static ulong MultiplyShiftRightShiftFirst(ulong value, ulong multiplier, int shift)
|
||||||
{
|
{
|
||||||
ulong high = value >> shift;
|
ulong high = value >> shift;
|
||||||
ulong mask = (1UL << shift) - 1UL;
|
ulong mask = (1UL << shift) - 1UL;
|
||||||
ulong low = value & mask;
|
ulong low = value & mask;
|
||||||
|
|
||||||
UInt128 highContribution = (UInt128)high * multiplier;
|
UInt128 highContribution = (UInt128)high * multiplier;
|
||||||
@@ -326,7 +327,7 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModWindowed(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
public static ulong Pow2MontgomeryModWindowedCpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
if (exponent == 0UL)
|
if (exponent == 0UL)
|
||||||
@@ -345,7 +346,78 @@ public static class ULongExtensions
|
|||||||
|
|
||||||
ulong result = divisor.MontgomeryOne;
|
ulong result = divisor.MontgomeryOne;
|
||||||
ulong nPrime = divisor.NPrime;
|
ulong nPrime = divisor.NPrime;
|
||||||
ulong[] oddPowers = InitializeMontgomeryOddPowers(divisor, modulus, nPrime, oddPowerCount);
|
ulong[] oddPowers = InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowerCount);
|
||||||
|
|
||||||
|
int index = bitLength - 1;
|
||||||
|
while (index >= 0)
|
||||||
|
{
|
||||||
|
if (((exponent >> index) & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
|
index--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int windowStart = index - windowSize + 1;
|
||||||
|
if (windowStart < 0)
|
||||||
|
{
|
||||||
|
windowStart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (((exponent >> windowStart) & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
windowStart++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int windowLength = index - windowStart + 1;
|
||||||
|
for (int square = 0; square < windowLength; square++)
|
||||||
|
{
|
||||||
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong mask = (1UL << windowLength) - 1UL;
|
||||||
|
ulong windowValue = (exponent >> windowStart) & mask;
|
||||||
|
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||||
|
ulong multiplier = oddPowers[tableIndex];
|
||||||
|
result = result.MontgomeryMultiply(multiplier, modulus, nPrime);
|
||||||
|
|
||||||
|
index = windowStart - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oddPowerCount >= PerfectNumberConstants.PooledArrayThreshold)
|
||||||
|
{
|
||||||
|
ArrayPool<ulong>.Shared.Return(oddPowers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepMontgomery)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2MontgomeryModWindowedGpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||||
|
{
|
||||||
|
ulong modulus = divisor.Modulus;
|
||||||
|
if (exponent == 0UL)
|
||||||
|
{
|
||||||
|
return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exponent <= Pow2WindowFallbackThreshold)
|
||||||
|
{
|
||||||
|
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bitLength = GetPortableBitLength(exponent);
|
||||||
|
int windowSize = GetWindowSize(bitLength);
|
||||||
|
int oddPowerCount = 1 << (windowSize - 1);
|
||||||
|
|
||||||
|
ulong result = divisor.MontgomeryOne;
|
||||||
|
ulong nPrime = divisor.NPrime;
|
||||||
|
ulong[] oddPowers = InitializeMontgomeryOddPowersGpu(divisor, modulus, nPrime, oddPowerCount);
|
||||||
|
|
||||||
int index = bitLength - 1;
|
int index = bitLength - 1;
|
||||||
while (index >= 0)
|
while (index >= 0)
|
||||||
@@ -461,7 +533,30 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static ulong[] InitializeMontgomeryOddPowers(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
private static ulong[] InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
||||||
|
{
|
||||||
|
ulong[] oddPowers = oddPowerCount < PerfectNumberConstants.PooledArrayThreshold ? new ulong[oddPowerCount] : ArrayPool<ulong>.Shared.Rent(oddPowerCount);
|
||||||
|
|
||||||
|
oddPowers[0] = divisor.MontgomeryTwo;
|
||||||
|
if (oddPowerCount == 1)
|
||||||
|
{
|
||||||
|
return oddPowers;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong previous,
|
||||||
|
square = divisor.MontgomeryTwoSquared;
|
||||||
|
|
||||||
|
for (int i = 1; i < oddPowerCount; i++)
|
||||||
|
{
|
||||||
|
previous = oddPowers[i - 1];
|
||||||
|
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return oddPowers;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong[] InitializeMontgomeryOddPowersGpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
||||||
{
|
{
|
||||||
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
||||||
oddPowers[0] = divisor.MontgomeryTwo;
|
oddPowers[0] = divisor.MontgomeryTwo;
|
||||||
@@ -481,17 +576,31 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModWithCycle(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
public static ulong Pow2MontgomeryModWithCycleCpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
ulong rotationCount = exponent % cycleLength;
|
ulong rotationCount = exponent % cycleLength;
|
||||||
return Pow2MontgomeryModWindowed(rotationCount, divisor, keepMontgomery: false);
|
return Pow2MontgomeryModWindowedCpu(rotationCount, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2MontgomeryModFromCycleRemainderCpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||||
|
{
|
||||||
|
return Pow2MontgomeryModWindowedCpu(reducedExponent, divisor, keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModFromCycleRemainder(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
return Pow2MontgomeryModWindowed(reducedExponent, divisor, keepMontgomery: false);
|
ulong rotationCount = exponent % cycleLength;
|
||||||
|
return Pow2MontgomeryModWindowedGpu(rotationCount, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2MontgomeryModFromCycleRemainderGpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||||
|
{
|
||||||
|
return Pow2MontgomeryModWindowedGpu(reducedExponent, divisor, keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user