--mersenne=bydivisor testers and sessions integrated and converted to structs.
This commit is contained in:
1 parent
3c80221105
commit
2abc6aae49
32 files changed
+3814
-1505
No files matched your search
@@ -0,0 +1,111 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public static class ByteExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte AddMod3(this byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 3;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte AddMod7(this byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 7;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte AddMod13(this byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 13;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte AddMod17(this byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 17;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte AddMod19(this byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 19;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte AddMod10(this byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 10;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte AddMod11(this byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 11;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte AddMod8(this byte value, byte delta)
|
||||
{
|
||||
return (byte)((value + delta) & 7);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
internal struct GpuUInt128WorkSet
|
||||
{
|
||||
public GpuUInt128 Step;
|
||||
public GpuUInt128 Divisor;
|
||||
public GpuUInt128 Limit;
|
||||
}
|
||||
@@ -3,23 +3,31 @@ using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelerator gpu, ComputationDevice orderDevice) : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
private Func<ulong, MontgomeryDivisorData, ulong> _getCycleLength = orderDevice switch
|
||||
internal struct MersenneCpuDivisorScanSessionWithCpuOrder() : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
ComputationDevice.Gpu => (divisor, divisorData) => DivisorCycleCache.Shared.GetCycleLengthGpu(gpu, divisor, divisorData),
|
||||
ComputationDevice.Hybrid => (divisor, divisorData) => DivisorCycleCache.Shared.GetCycleLengthHybrid(gpu, divisor, divisorData),
|
||||
_ => static (divisor, divisorData) => DivisorCycleCache.Shared.GetCycleLengthCpu(divisor, divisorData),
|
||||
};
|
||||
private readonly CycleRemainderStepper _cycleStepper = new();
|
||||
private ExponentRemainderStepperCpu? _exponentStepper;
|
||||
|
||||
public void Configure(PrimeOrderCalculatorAccelerator gpu, ComputationDevice orderDevice)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public readonly void Configure()
|
||||
{
|
||||
_getCycleLength = orderDevice switch
|
||||
}
|
||||
|
||||
private static ulong _hits;
|
||||
private static ulong _misses;
|
||||
|
||||
private static void PrintStats(bool wasHit)
|
||||
{
|
||||
if (wasHit)
|
||||
{
|
||||
ComputationDevice.Gpu => (divisor, divisorData) => DivisorCycleCache.Shared.GetCycleLengthGpu(gpu, divisor, divisorData),
|
||||
ComputationDevice.Hybrid => (divisor, divisorData) => DivisorCycleCache.Shared.GetCycleLengthHybrid(gpu, divisor, divisorData),
|
||||
_ => static (divisor, divisorData) => DivisorCycleCache.Shared.GetCycleLengthCpu(divisor, divisorData),
|
||||
};
|
||||
Interlocked.Increment(ref _hits);
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref _misses);
|
||||
}
|
||||
|
||||
Console.WriteLine($"MersenneCpuDivisorScanSession, ExponentStepper hits: $_hits, misses: $_misses");
|
||||
}
|
||||
|
||||
public bool CheckDivisor(
|
||||
@@ -45,7 +53,154 @@ internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelera
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = _getCycleLength(divisor, divisorData);
|
||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLengthCpu(divisor, divisorData);
|
||||
|
||||
// This never kicks off in production code
|
||||
// if (divisorCycle == 0UL)
|
||||
// {
|
||||
// // DivisorCycleCache guarantees a positive cycle for divisors greater than one.
|
||||
// throw new InvalidOperationException($"Divisor cycle solver returned zero for divisor {divisor}.");
|
||||
// }
|
||||
}
|
||||
|
||||
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
|
||||
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
|
||||
CycleRemainderStepper cycleStepper = _cycleStepper;
|
||||
cycleStepper.CycleLength = divisorCycle;
|
||||
if (cycleStepper.Initialize(primes[0]) == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_exponentStepper is not { } exponentStepper)
|
||||
{
|
||||
_exponentStepper = exponentStepper = new(divisorData, divisorCycle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!exponentStepper.MatchesDivisor(divisorData, divisorCycle))
|
||||
{
|
||||
_exponentStepper = exponentStepper = new(divisorData, divisorCycle);
|
||||
}
|
||||
}
|
||||
|
||||
if (exponentStepper.InitializeCpuIsUnity(primes[0]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 1; i < length; i++)
|
||||
{
|
||||
if (cycleStepper.ComputeNext(primes[i]) != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (exponentStepper.ComputeNextIsUnity(primes[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public void Return() => ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
|
||||
}
|
||||
|
||||
internal struct MersenneCpuDivisorScanSessionWithHybridOrder() : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
private PrimeOrderCalculatorAccelerator? _gpu;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void Configure(PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
_gpu = gpu;
|
||||
}
|
||||
|
||||
public bool CheckDivisor(
|
||||
ulong divisor,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
ulong divisorCycle,
|
||||
in ReadOnlySpan<ulong> primes)
|
||||
{
|
||||
int length = primes.Length;
|
||||
var gpu = _gpu ?? throw new InvalidOperationException("Session was not configured with an accelerator.");
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLengthHybrid(gpu, divisor, divisorData);
|
||||
}
|
||||
|
||||
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
|
||||
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
|
||||
var cycleStepper = new CycleRemainderStepper(divisorCycle);
|
||||
if (cycleStepper.Initialize(primes[0]) == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var exponentStepper = new ExponentRemainderStepperCpu(divisorData, divisorCycle);
|
||||
if (exponentStepper.InitializeCpuIsUnity(primes[0]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 1; i < length; i++)
|
||||
{
|
||||
if (cycleStepper.ComputeNext(primes[i]) != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (exponentStepper.ComputeNextIsUnity(primes[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public void Return() => ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
|
||||
}
|
||||
|
||||
internal struct MersenneCpuDivisorScanSessionWithGpuOrder() : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
private PrimeOrderCalculatorAccelerator? _gpu;
|
||||
|
||||
public void Configure(PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
_gpu = gpu;
|
||||
}
|
||||
|
||||
public bool CheckDivisor(
|
||||
ulong divisor,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
ulong divisorCycle,
|
||||
in ReadOnlySpan<ulong> primes)
|
||||
{
|
||||
int length = primes.Length;
|
||||
var gpu = _gpu ?? throw new InvalidOperationException("Session was not configured with an accelerator.");
|
||||
// EvenPerfectBitScanner always provides at least one exponent on this path.
|
||||
// Keep the guard commented out for tests that might reuse the session differently.
|
||||
// if (length == 0)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// This never kicks off in production code
|
||||
// if (divisorData.Modulus != divisor)
|
||||
// {
|
||||
// throw new InvalidOperationException("Divisor data is for a different modulus");
|
||||
// // cachedData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
// }
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLengthGpu(gpu, divisor, divisorData);
|
||||
|
||||
// This never kicks off in production code
|
||||
// if (divisorCycle == 0UL)
|
||||
// {
|
||||
@@ -84,6 +239,6 @@ internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelera
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Return() => ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public void Return() => ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
|
||||
}
|
||||
@@ -1,980 +0,0 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Globalization;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice orderDevice = ComputationDevice.Hybrid) : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
private BigInteger _divisorLimit;
|
||||
private int _batchSize = 1_024;
|
||||
private BigInteger _minK = BigInteger.One;
|
||||
private readonly ComputationDevice OrderDevice = orderDevice;
|
||||
private readonly TryCycleLengthDelegate _tryCalculateCycleLengthForExponent = orderDevice switch
|
||||
{
|
||||
ComputationDevice.Gpu => TryCalculateCycleLengthForExponentGpu,
|
||||
ComputationDevice.Hybrid => TryCalculateCycleLengthForExponentHybrid,
|
||||
_ => TryCalculateCycleLengthForExponentCpu,
|
||||
};
|
||||
|
||||
private readonly CalculateCycleLengthDelegate _calculateCycleLength = orderDevice switch
|
||||
{
|
||||
ComputationDevice.Gpu => CalculateCycleLengthGpu,
|
||||
ComputationDevice.Hybrid => CalculateCycleLengthHybrid,
|
||||
_ => CalculateCycleLengthCpu,
|
||||
};
|
||||
|
||||
private string? _stateFilePath;
|
||||
private int _stateCounter;
|
||||
private BigInteger _lastSavedK;
|
||||
|
||||
private delegate bool TryCycleLengthDelegate(
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed);
|
||||
|
||||
private delegate ulong CalculateCycleLengthDelegate(
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
bool skipPrimeOrderHeuristic);
|
||||
|
||||
[ThreadStatic]
|
||||
private static GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
||||
|
||||
private struct GpuUInt128WorkSet
|
||||
{
|
||||
public GpuUInt128 Step;
|
||||
public GpuUInt128 Divisor;
|
||||
public GpuUInt128 Limit;
|
||||
}
|
||||
|
||||
public int BatchSize
|
||||
{
|
||||
get => _batchSize;
|
||||
set => _batchSize = Math.Max(1, value);
|
||||
}
|
||||
|
||||
public BigInteger MinK
|
||||
{
|
||||
get => _minK;
|
||||
set => _minK = value < BigInteger.One ? BigInteger.One : value;
|
||||
}
|
||||
|
||||
public string? StateFilePath
|
||||
{
|
||||
get => _stateFilePath;
|
||||
set => _stateFilePath = value;
|
||||
}
|
||||
|
||||
public void ResetStateTracking()
|
||||
{
|
||||
_stateCounter = 0;
|
||||
}
|
||||
|
||||
public void ResumeFromState(BigInteger lastSavedK)
|
||||
{
|
||||
_lastSavedK = lastSavedK;
|
||||
_minK = lastSavedK + BigInteger.One;
|
||||
_stateCounter = 0;
|
||||
}
|
||||
|
||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
_divisorLimit = ComputeDivisorLimitFromMaxPrimeBig(maxPrime);
|
||||
}
|
||||
|
||||
public ulong DivisorLimit
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (_divisorLimit.IsZero)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return _divisorLimit > ulong.MaxValue ? ulong.MaxValue : (ulong)_divisorLimit;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||
|
||||
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
||||
{
|
||||
BigInteger allowedMax = ComputeAllowedMaxDivisorBig(prime, _divisorLimit);
|
||||
|
||||
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
|
||||
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
|
||||
// divisorsExhausted = true;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
bool composite = CheckDivisors(
|
||||
gpu,
|
||||
prime,
|
||||
allowedMax,
|
||||
_minK,
|
||||
out bool processedAll,
|
||||
out divisor);
|
||||
|
||||
if (composite)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisorsExhausted = processedAll || composite;
|
||||
divisor = BigInteger.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
ulong divisorLimit = GetAllowedDivisorLimitForSpan(_divisorLimit);
|
||||
int length = primes.Length;
|
||||
for (int index = 0; index < length; index++)
|
||||
{
|
||||
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu) => ThreadStaticPools.RentMersenneCpuDivisorSession(gpu, OrderDevice);
|
||||
|
||||
private bool CheckDivisors(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong prime,
|
||||
BigInteger allowedMax,
|
||||
BigInteger minK,
|
||||
out bool processedAll,
|
||||
out BigInteger foundDivisor)
|
||||
{
|
||||
BigInteger normalizedMinK = minK < BigInteger.One ? BigInteger.One : minK;
|
||||
BigInteger step = ((BigInteger)prime) << 1;
|
||||
BigInteger firstDivisor = (step * normalizedMinK) + BigInteger.One;
|
||||
bool unlimited = allowedMax.IsZero;
|
||||
|
||||
bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue;
|
||||
bool limitFits64 = unlimited || allowedMax <= ulong.MaxValue;
|
||||
if (fits64 && limitFits64)
|
||||
{
|
||||
ulong allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
|
||||
bool composite64 = CheckDivisors64Bit(
|
||||
gpu,
|
||||
prime,
|
||||
allowedMax64,
|
||||
(ulong)normalizedMinK,
|
||||
out processedAll,
|
||||
out ulong foundDivisor64);
|
||||
foundDivisor = foundDivisor64;
|
||||
return composite64;
|
||||
}
|
||||
|
||||
if (fits64 && allowedMax > ulong.MaxValue)
|
||||
{
|
||||
ulong allowedMax64 = ulong.MaxValue;
|
||||
bool composite64 = CheckDivisors64Bit(
|
||||
gpu,
|
||||
prime,
|
||||
allowedMax64,
|
||||
(ulong)normalizedMinK,
|
||||
out processedAll,
|
||||
out ulong foundDivisor64);
|
||||
if (composite64)
|
||||
{
|
||||
foundDivisor = foundDivisor64;
|
||||
return true;
|
||||
}
|
||||
|
||||
BigInteger iterations = ((BigInteger)(allowedMax64 - (ulong)firstDivisor) / (ulong)step) + BigInteger.One;
|
||||
BigInteger nextK = normalizedMinK + iterations;
|
||||
return CheckDivisorsLarge(
|
||||
gpu,
|
||||
prime,
|
||||
allowedMax,
|
||||
nextK,
|
||||
step,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
return CheckDivisorsLarge(
|
||||
gpu,
|
||||
prime,
|
||||
allowedMax,
|
||||
normalizedMinK,
|
||||
step,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
private bool CheckDivisors64Bit(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong prime,
|
||||
ulong allowedMax,
|
||||
ulong minK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
foundDivisor = 0UL;
|
||||
processedAll = true;
|
||||
ulong currentK = minK < 1UL ? 1UL : minK;
|
||||
|
||||
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
||||
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
|
||||
|
||||
ref GpuUInt128 step = ref workSet.Step;
|
||||
step.High = 0UL;
|
||||
step.Low = prime;
|
||||
step.ShiftLeft(1);
|
||||
|
||||
ref GpuUInt128 limit = ref workSet.Limit;
|
||||
limit.High = 0UL;
|
||||
limit.Low = allowedMax;
|
||||
|
||||
ref GpuUInt128 divisor = ref workSet.Divisor;
|
||||
divisor.High = step.High;
|
||||
divisor.Low = step.Low;
|
||||
divisor.Add(1UL);
|
||||
if (minK > 1UL)
|
||||
{
|
||||
GpuUInt128 offset = new(minK - 1UL);
|
||||
divisor = step * offset;
|
||||
divisor.Add(1UL);
|
||||
}
|
||||
|
||||
if (divisor.CompareTo(limit) > 0)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Intentionally recomputes factorizations without a per-thread cache.
|
||||
// The previous factor cache recorded virtually no hits and only slowed down the scan.
|
||||
|
||||
ulong stepHigh = step.High;
|
||||
ulong stepLow = step.Low;
|
||||
ulong limitHigh = limit.High;
|
||||
ulong divisorLow = divisor.Low;
|
||||
|
||||
if (stepHigh == 0UL && limitHigh == 0UL)
|
||||
{
|
||||
ulong maxK = allowedMax > 0UL ? (allowedMax - 1UL) / stepLow : 0UL;
|
||||
if (maxK == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong startK = minK < 1UL ? 1UL : minK;
|
||||
bool processedTop = true;
|
||||
bool processedBottom = true;
|
||||
bool composite = false;
|
||||
|
||||
if (startK <= maxK)
|
||||
{
|
||||
composite = CheckDivisors64Range(
|
||||
gpu,
|
||||
prime,
|
||||
stepLow,
|
||||
allowedMax,
|
||||
startK,
|
||||
maxK,
|
||||
ref currentK,
|
||||
out processedTop,
|
||||
out foundDivisor);
|
||||
if (composite)
|
||||
{
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ulong lowerEnd = startK > 1UL ? startK - 1UL : 0UL;
|
||||
if (lowerEnd >= 1UL && startK == 1UL)
|
||||
{
|
||||
lowerEnd = Math.Min(lowerEnd, maxK);
|
||||
currentK = 1UL;
|
||||
composite = CheckDivisors64Range(
|
||||
gpu,
|
||||
prime,
|
||||
stepLow,
|
||||
allowedMax,
|
||||
1UL,
|
||||
lowerEnd,
|
||||
ref currentK,
|
||||
out processedBottom,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
processedAll = processedTop && processedBottom;
|
||||
return composite;
|
||||
}
|
||||
|
||||
ulong divisorHigh = divisor.High;
|
||||
var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor);
|
||||
|
||||
while (divisor.CompareTo(limit) <= 0)
|
||||
{
|
||||
if (residueStepper.IsAdmissible())
|
||||
{
|
||||
ulong candidate = divisor.Low;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||
ulong divisorCycle;
|
||||
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
|
||||
if (!_tryCalculateCycleLengthForExponent(
|
||||
candidate,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
|
||||
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
|
||||
divisorCycle = _calculateCycleLength(
|
||||
candidate,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
RecordState(currentK);
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
|
||||
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
|
||||
foundDivisor = candidate;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
divisor.Add(step);
|
||||
currentK++;
|
||||
residueStepper.Advance();
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivisorsLarge(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong prime,
|
||||
BigInteger allowedMax,
|
||||
BigInteger minK,
|
||||
BigInteger step,
|
||||
out bool processedAll,
|
||||
out BigInteger foundDivisor)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = BigInteger.Zero;
|
||||
BigInteger currentK = minK < BigInteger.One ? BigInteger.One : minK;
|
||||
BigInteger divisor = (step * currentK) + BigInteger.One;
|
||||
_ = gpu;
|
||||
if (allowedMax.IsZero)
|
||||
{
|
||||
processedAll = false;
|
||||
return false;
|
||||
}
|
||||
if (divisor > allowedMax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
|
||||
|
||||
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
||||
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
||||
var rem5 = new BigIntegerCycleRemainderStepper(5);
|
||||
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
||||
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
||||
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
||||
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
||||
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
||||
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
||||
|
||||
byte remainder10 = (byte)rem10.Initialize(divisor);
|
||||
byte remainder8 = (byte)rem8.Initialize(divisor);
|
||||
byte remainder5 = (byte)rem5.Initialize(divisor);
|
||||
byte remainder3 = (byte)rem3.Initialize(divisor);
|
||||
byte remainder7 = (byte)rem7.Initialize(divisor);
|
||||
byte remainder11 = (byte)rem11.Initialize(divisor);
|
||||
byte remainder13 = (byte)rem13.Initialize(divisor);
|
||||
byte remainder17 = (byte)rem17.Initialize(divisor);
|
||||
byte remainder19 = (byte)rem19.Initialize(divisor);
|
||||
|
||||
while (divisor <= allowedMax)
|
||||
{
|
||||
bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
|
||||
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
|
||||
{
|
||||
if (IsProbablePrimeBigInteger(divisor))
|
||||
{
|
||||
BigInteger powResult = BigInteger.ModPow(2, prime, divisor);
|
||||
RecordState(currentK);
|
||||
if (powResult.IsOne)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RecordState(currentK);
|
||||
}
|
||||
}
|
||||
|
||||
currentK += BigInteger.One;
|
||||
divisor += step;
|
||||
remainder10 = (byte)rem10.ComputeNext(divisor);
|
||||
remainder8 = (byte)rem8.ComputeNext(divisor);
|
||||
remainder5 = (byte)rem5.ComputeNext(divisor);
|
||||
remainder3 = (byte)rem3.ComputeNext(divisor);
|
||||
remainder7 = (byte)rem7.ComputeNext(divisor);
|
||||
remainder11 = (byte)rem11.ComputeNext(divisor);
|
||||
remainder13 = (byte)rem13.ComputeNext(divisor);
|
||||
remainder17 = (byte)rem17.ComputeNext(divisor);
|
||||
remainder19 = (byte)rem19.ComputeNext(divisor);
|
||||
|
||||
if (divisor > allowedMax)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = BigInteger.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivisors64Range(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong allowedMax,
|
||||
ulong startK,
|
||||
ulong endK,
|
||||
ref ulong currentK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
if (startK < 1UL || endK < startK)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
currentK = startK;
|
||||
|
||||
UInt128 step128 = step;
|
||||
UInt128 allowedMax128 = allowedMax;
|
||||
UInt128 startDivisor128 = (step128 * startK) + UInt128.One;
|
||||
if (startDivisor128 > allowedMax128)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt128 rangeLimit128 = (step128 * endK) + UInt128.One;
|
||||
if (rangeLimit128 > allowedMax128)
|
||||
{
|
||||
rangeLimit128 = allowedMax128;
|
||||
}
|
||||
|
||||
ulong startDivisor = (ulong)startDivisor128;
|
||||
ulong rangeLimit = (ulong)rangeLimit128;
|
||||
|
||||
var residueStepper = new MersenneDivisorResidueStepper(prime, (GpuUInt128)step128, (GpuUInt128)startDivisor128);
|
||||
|
||||
return CheckDivisors64(
|
||||
gpu,
|
||||
prime,
|
||||
step,
|
||||
rangeLimit,
|
||||
startDivisor,
|
||||
ref residueStepper,
|
||||
ref currentK,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
private bool CheckDivisors64(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong limit,
|
||||
ulong divisor,
|
||||
ref MersenneDivisorResidueStepper residueStepper,
|
||||
ref ulong currentK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
bool canAdvance = step <= limit;
|
||||
if (!canAdvance)
|
||||
{
|
||||
if (divisor <= limit && residueStepper.IsAdmissible())
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!_tryCalculateCycleLengthForExponent(
|
||||
divisor,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = _calculateCycleLength(
|
||||
divisor,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor > limit)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong remainingIterations = ((limit - divisor) / step) + 1UL;
|
||||
while (true)
|
||||
{
|
||||
if (residueStepper.IsAdmissible())
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!_tryCalculateCycleLengthForExponent(
|
||||
divisor,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = _calculateCycleLength(
|
||||
divisor,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
RecordState(currentK);
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
remainingIterations--;
|
||||
if (remainingIterations == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisor += step;
|
||||
currentK++;
|
||||
residueStepper.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
ulong residue = prime.Pow2MontgomeryModWithCycleConvertToStandardCpu(divisorCycle, divisorData);
|
||||
return residue == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
private void RecordState(BigInteger k)
|
||||
{
|
||||
string? path = _stateFilePath;
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (k <= _lastSavedK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int next = _stateCounter + 1;
|
||||
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
|
||||
{
|
||||
string? directory = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.AppendAllText(path, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
|
||||
_stateCounter = 0;
|
||||
_lastSavedK = k;
|
||||
}
|
||||
else
|
||||
{
|
||||
_stateCounter = next;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsProbablePrimeBigInteger(BigInteger value)
|
||||
{
|
||||
if (value <= 3)
|
||||
{
|
||||
return value >= 2;
|
||||
}
|
||||
|
||||
if (value.IsEven)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BigInteger d = value - 1;
|
||||
int s = 0;
|
||||
while ((d & 1) == 0)
|
||||
{
|
||||
d >>= 1;
|
||||
s++;
|
||||
}
|
||||
|
||||
ReadOnlySpan<int> bases = stackalloc int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
|
||||
for (int i = 0; i < bases.Length; i++)
|
||||
{
|
||||
int baseValue = bases[i];
|
||||
if ((BigInteger)baseValue >= value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var expStepper = new BigIntegerExponentStepper(value, baseValue);
|
||||
BigInteger x = expStepper.Initialize(d);
|
||||
if (x == 1 || x == value - 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool witnessFound = true;
|
||||
for (int r = 1; r < s; r++)
|
||||
{
|
||||
BigInteger targetExponent = d << r;
|
||||
x = expStepper.ComputeNext(targetExponent);
|
||||
if (x == value - 1)
|
||||
{
|
||||
witnessFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (witnessFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryCalculateCycleLengthForExponentCpu(
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
out cycleLength,
|
||||
out primeOrderFailed);
|
||||
|
||||
private static bool TryCalculateCycleLengthForExponentHybrid(
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
out cycleLength,
|
||||
out primeOrderFailed);
|
||||
|
||||
private static bool TryCalculateCycleLengthForExponentGpu(
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentGpu(
|
||||
gpu,
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
out cycleLength,
|
||||
out primeOrderFailed);
|
||||
|
||||
private static ulong CalculateCycleLengthCpu(
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthCpu(
|
||||
divisor,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic);
|
||||
|
||||
private static ulong CalculateCycleLengthHybrid(
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic);
|
||||
|
||||
private static ulong CalculateCycleLengthGpu(
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthGpu(
|
||||
gpu,
|
||||
divisor,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod3(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 3;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod7(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 7;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod13(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 13;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod17(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 17;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod19(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 19;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod10(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 10;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod11(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 11;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod8(byte value, byte delta)
|
||||
{
|
||||
return (byte)((value + delta) & 7);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong GetAllowedDivisorLimitForSpan(BigInteger divisorLimit)
|
||||
{
|
||||
if (divisorLimit.IsZero)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return divisorLimit > ulong.MaxValue ? ulong.MaxValue : (ulong)divisorLimit;
|
||||
}
|
||||
|
||||
private static BigInteger ComputeDivisorLimitFromMaxPrimeBig(ulong maxPrime)
|
||||
{
|
||||
if (maxPrime <= 1UL)
|
||||
{
|
||||
return BigInteger.Zero;
|
||||
}
|
||||
|
||||
if (maxPrime - 1UL >= 64UL)
|
||||
{
|
||||
return (BigInteger.One << 256) - BigInteger.One;
|
||||
}
|
||||
|
||||
return (BigInteger.One << (int)(maxPrime - 1UL)) - BigInteger.One;
|
||||
}
|
||||
|
||||
private static BigInteger ComputeAllowedMaxDivisorBig(ulong prime, BigInteger divisorLimit)
|
||||
{
|
||||
if (prime <= 1UL)
|
||||
{
|
||||
return BigInteger.Zero;
|
||||
}
|
||||
|
||||
if (divisorLimit.IsZero || prime - 1UL >= 64UL)
|
||||
{
|
||||
return divisorLimit;
|
||||
}
|
||||
|
||||
BigInteger candidateLimit = (BigInteger.One << (int)(prime - 1UL)) - BigInteger.One;
|
||||
if (divisorLimit.IsZero)
|
||||
{
|
||||
return candidateLimit;
|
||||
}
|
||||
|
||||
return BigInteger.Min(candidateLimit, divisorLimit);
|
||||
}
|
||||
|
||||
private static ulong ComputeAllowedMaxDivisor(ulong prime, BigInteger divisorLimit)
|
||||
{
|
||||
BigInteger allowed = ComputeAllowedMaxDivisorBig(prime, divisorLimit);
|
||||
if (allowed.IsZero)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return allowed > ulong.MaxValue ? ulong.MaxValue : (ulong)allowed;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Globalization;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public struct MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder() : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
public BigInteger DivisorLimit;
|
||||
public BigInteger MinK = EnvironmentConfiguration.MinK;
|
||||
|
||||
#pragma warning disable CS8618 // StateFilePath is always set on EvenPerfectBitScanner execution path when ResumeFromState is called.
|
||||
private string StateFilePath;
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
private int _stateCounter;
|
||||
private BigInteger _lastSavedK;
|
||||
|
||||
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void ResetStateTracking()
|
||||
{
|
||||
_stateCounter = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
|
||||
{
|
||||
StateFilePath = stateFile;
|
||||
_lastSavedK = lastSavedK;
|
||||
MinK = minK;
|
||||
_stateCounter = 0;
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
||||
{
|
||||
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
|
||||
|
||||
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
|
||||
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
|
||||
// divisorsExhausted = true;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
bool composite = CheckDivisors(
|
||||
prime,
|
||||
allowedMax,
|
||||
MinK,
|
||||
out bool processedAll,
|
||||
out divisor);
|
||||
|
||||
if (composite)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisorsExhausted = processedAll || composite;
|
||||
divisor = BigInteger.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
BigInteger divisorLimitBigInteger = MersenneNumberDivisorByDivisorTester.ComputeDivisorLimitFromMaxPrimeBig(maxPrime);
|
||||
DivisorLimit = divisorLimitBigInteger;
|
||||
ulong divisorLimit = MersenneNumberDivisorByDivisorTester.GetAllowedDivisorLimitForSpan(divisorLimitBigInteger);
|
||||
int length = primes.Length;
|
||||
for (int index = 0; index < length; index++)
|
||||
{
|
||||
allowedMaxValues[index] = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public readonly IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu) => new MersenneCpuDivisorScanSessionWithCpuOrder();
|
||||
|
||||
private bool CheckDivisors(
|
||||
ulong prime,
|
||||
BigInteger allowedMax,
|
||||
BigInteger minK,
|
||||
out bool processedAll,
|
||||
out BigInteger foundDivisor)
|
||||
{
|
||||
BigInteger normalizedMinK = minK < BigInteger.One ? BigInteger.One : minK;
|
||||
BigInteger step = ((BigInteger)prime) << 1;
|
||||
BigInteger firstDivisor = (step * normalizedMinK) + BigInteger.One;
|
||||
bool unlimited = allowedMax.IsZero;
|
||||
|
||||
bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue;
|
||||
bool limitFits64 = unlimited || allowedMax <= ulong.MaxValue;
|
||||
if (fits64 && limitFits64)
|
||||
{
|
||||
ulong allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
|
||||
bool composite64 = CheckDivisors64Bit(
|
||||
prime,
|
||||
allowedMax64,
|
||||
(ulong)normalizedMinK,
|
||||
out processedAll,
|
||||
out ulong foundDivisor64);
|
||||
foundDivisor = foundDivisor64;
|
||||
return composite64;
|
||||
}
|
||||
|
||||
if (fits64 && allowedMax > ulong.MaxValue)
|
||||
{
|
||||
ulong allowedMax64 = ulong.MaxValue;
|
||||
bool composite64 = CheckDivisors64Bit(
|
||||
prime,
|
||||
allowedMax64,
|
||||
(ulong)normalizedMinK,
|
||||
out processedAll,
|
||||
out ulong foundDivisor64);
|
||||
if (composite64)
|
||||
{
|
||||
foundDivisor = foundDivisor64;
|
||||
return true;
|
||||
}
|
||||
|
||||
BigInteger iterations = ((BigInteger)(allowedMax64 - (ulong)firstDivisor) / (ulong)step) + BigInteger.One;
|
||||
BigInteger nextK = normalizedMinK + iterations;
|
||||
return CheckDivisorsLarge(
|
||||
prime,
|
||||
allowedMax,
|
||||
nextK,
|
||||
step,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
return CheckDivisorsLarge(
|
||||
prime,
|
||||
allowedMax,
|
||||
normalizedMinK,
|
||||
step,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
private bool CheckDivisors64Bit(
|
||||
ulong prime,
|
||||
ulong allowedMax,
|
||||
ulong minK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
foundDivisor = 0UL;
|
||||
ulong currentK = minK < 1UL ? 1UL : minK;
|
||||
|
||||
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
||||
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
|
||||
|
||||
ref GpuUInt128 step = ref workSet.Step;
|
||||
step.High = 0UL;
|
||||
step.Low = prime;
|
||||
step.ShiftLeft(1);
|
||||
|
||||
ref GpuUInt128 limit = ref workSet.Limit;
|
||||
limit.High = 0UL;
|
||||
limit.Low = allowedMax;
|
||||
|
||||
ref GpuUInt128 divisor = ref workSet.Divisor;
|
||||
divisor.High = step.High;
|
||||
divisor.Low = step.Low;
|
||||
divisor.Add(1UL);
|
||||
if (minK > 1UL)
|
||||
{
|
||||
GpuUInt128 offset = new(minK - 1UL);
|
||||
divisor = step * offset;
|
||||
divisor.Add(1UL);
|
||||
}
|
||||
|
||||
if (divisor.CompareTo(limit) > 0)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Intentionally recomputes factorizations without a per-thread cache.
|
||||
// The previous factor cache recorded virtually no hits and only slowed down the scan.
|
||||
|
||||
ulong stepHigh = step.High;
|
||||
ulong stepLow = step.Low;
|
||||
ulong limitHigh = limit.High;
|
||||
|
||||
if (stepHigh == 0UL && limitHigh == 0UL)
|
||||
{
|
||||
ulong maxK = allowedMax > 0UL ? (allowedMax - 1UL) / stepLow : 0UL;
|
||||
if (maxK == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong startK = minK < 1UL ? 1UL : minK;
|
||||
bool processedTop = true;
|
||||
bool processedBottom = true;
|
||||
bool composite = false;
|
||||
|
||||
if (startK <= maxK)
|
||||
{
|
||||
composite = CheckDivisors64Range(
|
||||
prime,
|
||||
stepLow,
|
||||
allowedMax,
|
||||
startK,
|
||||
maxK,
|
||||
ref currentK,
|
||||
out processedTop,
|
||||
out foundDivisor);
|
||||
if (composite)
|
||||
{
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ulong lowerEnd = startK > 1UL ? startK - 1UL : 0UL;
|
||||
if (lowerEnd >= 1UL && startK == 1UL)
|
||||
{
|
||||
lowerEnd = Math.Min(lowerEnd, maxK);
|
||||
currentK = 1UL;
|
||||
composite = CheckDivisors64Range(
|
||||
prime,
|
||||
stepLow,
|
||||
allowedMax,
|
||||
1UL,
|
||||
lowerEnd,
|
||||
ref currentK,
|
||||
out processedBottom,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
processedAll = processedTop && processedBottom;
|
||||
return composite;
|
||||
}
|
||||
|
||||
var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor);
|
||||
|
||||
while (divisor.CompareTo(limit) <= 0)
|
||||
{
|
||||
if (residueStepper.IsAdmissible())
|
||||
{
|
||||
ulong candidate = divisor.Low;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||
ulong divisorCycle;
|
||||
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
|
||||
candidate,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
|
||||
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
|
||||
candidate,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
RecordState(StateFilePath, currentK);
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
|
||||
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
|
||||
foundDivisor = candidate;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
divisor.Add(step);
|
||||
currentK++;
|
||||
residueStepper.Advance();
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivisorsLarge(
|
||||
ulong prime,
|
||||
BigInteger allowedMax,
|
||||
BigInteger minK,
|
||||
BigInteger step,
|
||||
out bool processedAll,
|
||||
out BigInteger foundDivisor)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = BigInteger.Zero;
|
||||
BigInteger currentK = minK < BigInteger.One ? BigInteger.One : minK;
|
||||
BigInteger divisor = (step * currentK) + BigInteger.One;
|
||||
if (allowedMax.IsZero)
|
||||
{
|
||||
processedAll = false;
|
||||
return false;
|
||||
}
|
||||
if (divisor > allowedMax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
|
||||
|
||||
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
||||
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
||||
var rem5 = new BigIntegerCycleRemainderStepper(5);
|
||||
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
||||
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
||||
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
||||
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
||||
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
||||
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
||||
|
||||
byte remainder10 = (byte)rem10.Initialize(divisor);
|
||||
byte remainder8 = (byte)rem8.Initialize(divisor);
|
||||
byte remainder5 = (byte)rem5.Initialize(divisor);
|
||||
byte remainder3 = (byte)rem3.Initialize(divisor);
|
||||
byte remainder7 = (byte)rem7.Initialize(divisor);
|
||||
byte remainder11 = (byte)rem11.Initialize(divisor);
|
||||
byte remainder13 = (byte)rem13.Initialize(divisor);
|
||||
byte remainder17 = (byte)rem17.Initialize(divisor);
|
||||
byte remainder19 = (byte)rem19.Initialize(divisor);
|
||||
|
||||
while (divisor <= allowedMax)
|
||||
{
|
||||
bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
|
||||
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
|
||||
{
|
||||
if (MersenneNumberDivisorByDivisorTester.IsProbablePrimeBigInteger(divisor))
|
||||
{
|
||||
BigInteger powResult = BigInteger.ModPow(2, prime, divisor);
|
||||
RecordState(StateFilePath, currentK);
|
||||
if (powResult.IsOne)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RecordState(StateFilePath, currentK);
|
||||
}
|
||||
}
|
||||
|
||||
currentK += BigInteger.One;
|
||||
divisor += step;
|
||||
remainder10 = (byte)rem10.ComputeNext(divisor);
|
||||
remainder8 = (byte)rem8.ComputeNext(divisor);
|
||||
remainder5 = (byte)rem5.ComputeNext(divisor);
|
||||
remainder3 = (byte)rem3.ComputeNext(divisor);
|
||||
remainder7 = (byte)rem7.ComputeNext(divisor);
|
||||
remainder11 = (byte)rem11.ComputeNext(divisor);
|
||||
remainder13 = (byte)rem13.ComputeNext(divisor);
|
||||
remainder17 = (byte)rem17.ComputeNext(divisor);
|
||||
remainder19 = (byte)rem19.ComputeNext(divisor);
|
||||
|
||||
if (divisor > allowedMax)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = BigInteger.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivisors64Range(
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong allowedMax,
|
||||
ulong startK,
|
||||
ulong endK,
|
||||
ref ulong currentK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
if (startK < 1UL || endK < startK)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
currentK = startK;
|
||||
|
||||
UInt128 step128 = step;
|
||||
UInt128 allowedMax128 = allowedMax;
|
||||
UInt128 startDivisor128 = (step128 * startK) + UInt128.One;
|
||||
if (startDivisor128 > allowedMax128)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt128 rangeLimit128 = (step128 * endK) + UInt128.One;
|
||||
if (rangeLimit128 > allowedMax128)
|
||||
{
|
||||
rangeLimit128 = allowedMax128;
|
||||
}
|
||||
|
||||
ulong startDivisor = (ulong)startDivisor128;
|
||||
ulong rangeLimit = (ulong)rangeLimit128;
|
||||
|
||||
var residueStepper = new MersenneDivisorResidueStepper(prime, (GpuUInt128)step128, (GpuUInt128)startDivisor128);
|
||||
|
||||
return CheckDivisors64(
|
||||
prime,
|
||||
step,
|
||||
rangeLimit,
|
||||
startDivisor,
|
||||
ref residueStepper,
|
||||
ref currentK,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
private bool CheckDivisors64(
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong limit,
|
||||
ulong divisor,
|
||||
ref MersenneDivisorResidueStepper residueStepper,
|
||||
ref ulong currentK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
bool canAdvance = step <= limit;
|
||||
if (!canAdvance)
|
||||
{
|
||||
if (divisor <= limit && residueStepper.IsAdmissible())
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
|
||||
divisor,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
|
||||
divisor,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor > limit)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong remainingIterations = ((limit - divisor) / step) + 1UL;
|
||||
while (true)
|
||||
{
|
||||
if (residueStepper.IsAdmissible())
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentCpu(
|
||||
divisor,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthCpu(
|
||||
divisor,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
RecordState(StateFilePath, currentK);
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
remainingIterations--;
|
||||
if (remainingIterations == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisor += step;
|
||||
currentK++;
|
||||
residueStepper.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
private void RecordState(in string path, BigInteger k)
|
||||
{
|
||||
|
||||
if (k <= _lastSavedK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int next = _stateCounter + 1;
|
||||
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
|
||||
{
|
||||
string? directory = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.AppendAllText(path, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
|
||||
_stateCounter = 0;
|
||||
_lastSavedK = k;
|
||||
}
|
||||
else
|
||||
{
|
||||
_stateCounter = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Globalization;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public struct MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder() : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
public BigInteger DivisorLimit;
|
||||
public BigInteger MinK = EnvironmentConfiguration.MinK;
|
||||
public readonly PrimeOrderCalculatorAccelerator Accelerator = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
|
||||
#pragma warning disable CS8618 // StateFilePath is always set on EvenPerfectBitScanner execution path when ResumeFromState is called.
|
||||
private string StateFilePath;
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
private int _stateCounter;
|
||||
private BigInteger _lastSavedK;
|
||||
|
||||
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void ResetStateTracking()
|
||||
{
|
||||
_stateCounter = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
|
||||
{
|
||||
StateFilePath = stateFile;
|
||||
_lastSavedK = lastSavedK;
|
||||
MinK = minK;
|
||||
_stateCounter = 0;
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
||||
{
|
||||
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
|
||||
|
||||
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
|
||||
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
|
||||
// divisorsExhausted = true;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
bool composite = CheckDivisors(
|
||||
prime,
|
||||
allowedMax,
|
||||
MinK,
|
||||
out bool processedAll,
|
||||
out divisor);
|
||||
|
||||
if (composite)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisorsExhausted = processedAll || composite;
|
||||
divisor = BigInteger.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
BigInteger divisorLimitBigInteger = MersenneNumberDivisorByDivisorTester.ComputeDivisorLimitFromMaxPrimeBig(maxPrime);
|
||||
DivisorLimit = divisorLimitBigInteger;
|
||||
ulong divisorLimit = MersenneNumberDivisorByDivisorTester.GetAllowedDivisorLimitForSpan(divisorLimitBigInteger);
|
||||
int length = primes.Length;
|
||||
for (int index = 0; index < length; index++)
|
||||
{
|
||||
allowedMaxValues[index] = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
var session = new MersenneCpuDivisorScanSessionWithGpuOrder();
|
||||
session.Configure(gpu);
|
||||
return session;
|
||||
}
|
||||
|
||||
private bool CheckDivisors(
|
||||
ulong prime,
|
||||
BigInteger allowedMax,
|
||||
BigInteger minK,
|
||||
out bool processedAll,
|
||||
out BigInteger foundDivisor)
|
||||
{
|
||||
BigInteger normalizedMinK = minK < BigInteger.One ? BigInteger.One : minK;
|
||||
BigInteger step = ((BigInteger)prime) << 1;
|
||||
BigInteger firstDivisor = (step * normalizedMinK) + BigInteger.One;
|
||||
bool unlimited = allowedMax.IsZero;
|
||||
|
||||
bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue;
|
||||
bool limitFits64 = unlimited || allowedMax <= ulong.MaxValue;
|
||||
if (fits64 && limitFits64)
|
||||
{
|
||||
ulong allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
|
||||
bool composite64 = CheckDivisors64Bit(
|
||||
prime,
|
||||
allowedMax64,
|
||||
(ulong)normalizedMinK,
|
||||
out processedAll,
|
||||
out ulong foundDivisor64);
|
||||
foundDivisor = foundDivisor64;
|
||||
return composite64;
|
||||
}
|
||||
|
||||
if (fits64 && allowedMax > ulong.MaxValue)
|
||||
{
|
||||
ulong allowedMax64 = ulong.MaxValue;
|
||||
bool composite64 = CheckDivisors64Bit(
|
||||
prime,
|
||||
allowedMax64,
|
||||
(ulong)normalizedMinK,
|
||||
out processedAll,
|
||||
out ulong foundDivisor64);
|
||||
if (composite64)
|
||||
{
|
||||
foundDivisor = foundDivisor64;
|
||||
return true;
|
||||
}
|
||||
|
||||
BigInteger iterations = ((BigInteger)(allowedMax64 - (ulong)firstDivisor) / (ulong)step) + BigInteger.One;
|
||||
BigInteger nextK = normalizedMinK + iterations;
|
||||
return CheckDivisorsLarge(
|
||||
prime,
|
||||
allowedMax,
|
||||
nextK,
|
||||
step,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
return CheckDivisorsLarge(
|
||||
prime,
|
||||
allowedMax,
|
||||
normalizedMinK,
|
||||
step,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
private bool CheckDivisors64Bit(
|
||||
ulong prime,
|
||||
ulong allowedMax,
|
||||
ulong minK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
foundDivisor = 0UL;
|
||||
ulong currentK = minK < 1UL ? 1UL : minK;
|
||||
|
||||
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
||||
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
|
||||
|
||||
ref GpuUInt128 step = ref workSet.Step;
|
||||
step.High = 0UL;
|
||||
step.Low = prime;
|
||||
step.ShiftLeft(1);
|
||||
|
||||
ref GpuUInt128 limit = ref workSet.Limit;
|
||||
limit.High = 0UL;
|
||||
limit.Low = allowedMax;
|
||||
|
||||
ref GpuUInt128 divisor = ref workSet.Divisor;
|
||||
divisor.High = step.High;
|
||||
divisor.Low = step.Low;
|
||||
divisor.Add(1UL);
|
||||
if (minK > 1UL)
|
||||
{
|
||||
GpuUInt128 offset = new(minK - 1UL);
|
||||
divisor = step * offset;
|
||||
divisor.Add(1UL);
|
||||
}
|
||||
|
||||
if (divisor.CompareTo(limit) > 0)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Intentionally recomputes factorizations without a per-thread cache.
|
||||
// The previous factor cache recorded virtually no hits and only slowed down the scan.
|
||||
|
||||
ulong stepHigh = step.High;
|
||||
ulong stepLow = step.Low;
|
||||
ulong limitHigh = limit.High;
|
||||
|
||||
if (stepHigh == 0UL && limitHigh == 0UL)
|
||||
{
|
||||
ulong maxK = allowedMax > 0UL ? (allowedMax - 1UL) / stepLow : 0UL;
|
||||
if (maxK == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong startK = minK < 1UL ? 1UL : minK;
|
||||
bool processedTop = true;
|
||||
bool processedBottom = true;
|
||||
bool composite = false;
|
||||
|
||||
if (startK <= maxK)
|
||||
{
|
||||
composite = CheckDivisors64Range(
|
||||
prime,
|
||||
stepLow,
|
||||
allowedMax,
|
||||
startK,
|
||||
maxK,
|
||||
ref currentK,
|
||||
out processedTop,
|
||||
out foundDivisor);
|
||||
if (composite)
|
||||
{
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ulong lowerEnd = startK > 1UL ? startK - 1UL : 0UL;
|
||||
if (lowerEnd >= 1UL && startK == 1UL)
|
||||
{
|
||||
lowerEnd = Math.Min(lowerEnd, maxK);
|
||||
currentK = 1UL;
|
||||
composite = CheckDivisors64Range(
|
||||
prime,
|
||||
stepLow,
|
||||
allowedMax,
|
||||
1UL,
|
||||
lowerEnd,
|
||||
ref currentK,
|
||||
out processedBottom,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
processedAll = processedTop && processedBottom;
|
||||
return composite;
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator gpu = Accelerator;
|
||||
var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor);
|
||||
while (divisor.CompareTo(limit) <= 0)
|
||||
{
|
||||
if (residueStepper.IsAdmissible())
|
||||
{
|
||||
ulong candidate = divisor.Low;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||
ulong divisorCycle;
|
||||
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
|
||||
candidate,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
|
||||
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
|
||||
|
||||
candidate,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
RecordState(currentK);
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
|
||||
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
|
||||
foundDivisor = candidate;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
divisor.Add(step);
|
||||
currentK++;
|
||||
residueStepper.Advance();
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivisorsLarge(
|
||||
ulong prime,
|
||||
BigInteger allowedMax,
|
||||
BigInteger minK,
|
||||
BigInteger step,
|
||||
out bool processedAll,
|
||||
out BigInteger foundDivisor)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = BigInteger.Zero;
|
||||
BigInteger currentK = minK < BigInteger.One ? BigInteger.One : minK;
|
||||
BigInteger divisor = (step * currentK) + BigInteger.One;
|
||||
if (allowedMax.IsZero)
|
||||
{
|
||||
processedAll = false;
|
||||
return false;
|
||||
}
|
||||
if (divisor > allowedMax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
|
||||
|
||||
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
||||
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
||||
var rem5 = new BigIntegerCycleRemainderStepper(5);
|
||||
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
||||
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
||||
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
||||
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
||||
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
||||
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
||||
|
||||
byte remainder10 = (byte)rem10.Initialize(divisor);
|
||||
byte remainder8 = (byte)rem8.Initialize(divisor);
|
||||
byte remainder5 = (byte)rem5.Initialize(divisor);
|
||||
byte remainder3 = (byte)rem3.Initialize(divisor);
|
||||
byte remainder7 = (byte)rem7.Initialize(divisor);
|
||||
byte remainder11 = (byte)rem11.Initialize(divisor);
|
||||
byte remainder13 = (byte)rem13.Initialize(divisor);
|
||||
byte remainder17 = (byte)rem17.Initialize(divisor);
|
||||
byte remainder19 = (byte)rem19.Initialize(divisor);
|
||||
|
||||
while (divisor <= allowedMax)
|
||||
{
|
||||
bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
|
||||
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
|
||||
{
|
||||
if (MersenneNumberDivisorByDivisorTester.IsProbablePrimeBigInteger(divisor))
|
||||
{
|
||||
BigInteger powResult = BigInteger.ModPow(2, prime, divisor);
|
||||
RecordState(currentK);
|
||||
if (powResult.IsOne)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RecordState(currentK);
|
||||
}
|
||||
}
|
||||
|
||||
currentK += BigInteger.One;
|
||||
divisor += step;
|
||||
remainder10 = (byte)rem10.ComputeNext(divisor);
|
||||
remainder8 = (byte)rem8.ComputeNext(divisor);
|
||||
remainder5 = (byte)rem5.ComputeNext(divisor);
|
||||
remainder3 = (byte)rem3.ComputeNext(divisor);
|
||||
remainder7 = (byte)rem7.ComputeNext(divisor);
|
||||
remainder11 = (byte)rem11.ComputeNext(divisor);
|
||||
remainder13 = (byte)rem13.ComputeNext(divisor);
|
||||
remainder17 = (byte)rem17.ComputeNext(divisor);
|
||||
remainder19 = (byte)rem19.ComputeNext(divisor);
|
||||
|
||||
if (divisor > allowedMax)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = BigInteger.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivisors64Range(
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong allowedMax,
|
||||
ulong startK,
|
||||
ulong endK,
|
||||
ref ulong currentK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
if (startK < 1UL || endK < startK)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
currentK = startK;
|
||||
|
||||
UInt128 step128 = step;
|
||||
UInt128 allowedMax128 = allowedMax;
|
||||
UInt128 startDivisor128 = (step128 * startK) + UInt128.One;
|
||||
if (startDivisor128 > allowedMax128)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt128 rangeLimit128 = (step128 * endK) + UInt128.One;
|
||||
if (rangeLimit128 > allowedMax128)
|
||||
{
|
||||
rangeLimit128 = allowedMax128;
|
||||
}
|
||||
|
||||
ulong startDivisor = (ulong)startDivisor128;
|
||||
ulong rangeLimit = (ulong)rangeLimit128;
|
||||
|
||||
var residueStepper = new MersenneDivisorResidueStepper(prime, (GpuUInt128)step128, (GpuUInt128)startDivisor128);
|
||||
|
||||
return CheckDivisors64(
|
||||
prime,
|
||||
step,
|
||||
rangeLimit,
|
||||
startDivisor,
|
||||
ref residueStepper,
|
||||
ref currentK,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
private bool CheckDivisors64(
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong limit,
|
||||
ulong divisor,
|
||||
ref MersenneDivisorResidueStepper residueStepper,
|
||||
ref ulong currentK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator gpu = Accelerator;
|
||||
|
||||
if (step > limit)
|
||||
{
|
||||
if (divisor <= limit && residueStepper.IsAdmissible())
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
|
||||
divisor,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
|
||||
divisor,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor > limit)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong remainingIterations = ((limit - divisor) / step) + 1UL;
|
||||
while (true)
|
||||
{
|
||||
if (residueStepper.IsAdmissible())
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentGpu(
|
||||
divisor,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthGpu(
|
||||
divisor,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
RecordState(currentK);
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
remainingIterations--;
|
||||
if (remainingIterations == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisor += step;
|
||||
currentK++;
|
||||
residueStepper.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
private void RecordState(BigInteger k)
|
||||
{
|
||||
string path = StateFilePath;
|
||||
|
||||
if (k <= _lastSavedK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int next = _stateCounter + 1;
|
||||
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
|
||||
{
|
||||
string? directory = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.AppendAllText(path, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
|
||||
_stateCounter = 0;
|
||||
_lastSavedK = k;
|
||||
}
|
||||
else
|
||||
{
|
||||
_stateCounter = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,598 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Globalization;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public struct MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder() : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
public BigInteger DivisorLimit;
|
||||
public BigInteger MinK = EnvironmentConfiguration.MinK;
|
||||
public readonly PrimeOrderCalculatorAccelerator Accelerator = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
|
||||
#pragma warning disable CS8618 // StateFilePath is always set on EvenPerfectBitScanner execution path when ResumeFromState is called.
|
||||
private string StateFilePath;
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
private int _stateCounter;
|
||||
private BigInteger _lastSavedK;
|
||||
|
||||
private GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
||||
|
||||
public void ResetStateTracking()
|
||||
{
|
||||
_stateCounter = 0;
|
||||
}
|
||||
|
||||
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
|
||||
{
|
||||
StateFilePath = stateFile;
|
||||
_lastSavedK = lastSavedK;
|
||||
MinK = minK;
|
||||
_stateCounter = 0;
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
||||
{
|
||||
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
|
||||
|
||||
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
|
||||
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
|
||||
// divisorsExhausted = true;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
bool composite = CheckDivisors(
|
||||
prime,
|
||||
allowedMax,
|
||||
MinK,
|
||||
out bool processedAll,
|
||||
out divisor);
|
||||
|
||||
if (composite)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisorsExhausted = processedAll || composite;
|
||||
divisor = BigInteger.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
BigInteger divisorLimitBigInteger = MersenneNumberDivisorByDivisorTester.ComputeDivisorLimitFromMaxPrimeBig(maxPrime);
|
||||
DivisorLimit = divisorLimitBigInteger;
|
||||
ulong divisorLimit = MersenneNumberDivisorByDivisorTester.GetAllowedDivisorLimitForSpan(divisorLimitBigInteger);
|
||||
int length = primes.Length;
|
||||
for (int index = 0; index < length; index++)
|
||||
{
|
||||
allowedMaxValues[index] = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public readonly IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
var session = new MersenneCpuDivisorScanSessionWithHybridOrder();
|
||||
session.Configure(gpu);
|
||||
return session;
|
||||
}
|
||||
|
||||
private bool CheckDivisors(
|
||||
ulong prime,
|
||||
BigInteger allowedMax,
|
||||
BigInteger minK,
|
||||
out bool processedAll,
|
||||
out BigInteger foundDivisor)
|
||||
{
|
||||
BigInteger normalizedMinK = minK < BigInteger.One ? BigInteger.One : minK;
|
||||
BigInteger step = ((BigInteger)prime) << 1;
|
||||
BigInteger firstDivisor = (step * normalizedMinK) + BigInteger.One;
|
||||
bool unlimited = allowedMax.IsZero;
|
||||
|
||||
bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue;
|
||||
bool limitFits64 = unlimited || allowedMax <= ulong.MaxValue;
|
||||
if (fits64 && limitFits64)
|
||||
{
|
||||
ulong allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
|
||||
bool composite64 = CheckDivisors64Bit(
|
||||
prime,
|
||||
allowedMax64,
|
||||
(ulong)normalizedMinK,
|
||||
out processedAll,
|
||||
out ulong foundDivisor64);
|
||||
foundDivisor = foundDivisor64;
|
||||
return composite64;
|
||||
}
|
||||
|
||||
if (fits64 && allowedMax > ulong.MaxValue)
|
||||
{
|
||||
ulong allowedMax64 = ulong.MaxValue;
|
||||
bool composite64 = CheckDivisors64Bit(
|
||||
prime,
|
||||
allowedMax64,
|
||||
(ulong)normalizedMinK,
|
||||
out processedAll,
|
||||
out ulong foundDivisor64);
|
||||
if (composite64)
|
||||
{
|
||||
foundDivisor = foundDivisor64;
|
||||
return true;
|
||||
}
|
||||
|
||||
BigInteger iterations = ((BigInteger)(allowedMax64 - (ulong)firstDivisor) / (ulong)step) + BigInteger.One;
|
||||
BigInteger nextK = normalizedMinK + iterations;
|
||||
return CheckDivisorsLarge(
|
||||
prime,
|
||||
allowedMax,
|
||||
nextK,
|
||||
step,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
return CheckDivisorsLarge(
|
||||
prime,
|
||||
allowedMax,
|
||||
normalizedMinK,
|
||||
step,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
private bool CheckDivisors64Bit(
|
||||
ulong prime,
|
||||
ulong allowedMax,
|
||||
ulong minK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
foundDivisor = 0UL;
|
||||
ulong currentK = minK < 1UL ? 1UL : minK;
|
||||
|
||||
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
||||
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
|
||||
|
||||
ref GpuUInt128 step = ref workSet.Step;
|
||||
step.High = 0UL;
|
||||
step.Low = prime;
|
||||
step.ShiftLeft(1);
|
||||
|
||||
ref GpuUInt128 limit = ref workSet.Limit;
|
||||
limit.High = 0UL;
|
||||
limit.Low = allowedMax;
|
||||
|
||||
ref GpuUInt128 divisor = ref workSet.Divisor;
|
||||
divisor.High = step.High;
|
||||
divisor.Low = step.Low;
|
||||
divisor.Add(1UL);
|
||||
if (minK > 1UL)
|
||||
{
|
||||
GpuUInt128 offset = new(minK - 1UL);
|
||||
divisor = step * offset;
|
||||
divisor.Add(1UL);
|
||||
}
|
||||
|
||||
if (divisor.CompareTo(limit) > 0)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Intentionally recomputes factorizations without a per-thread cache.
|
||||
// The previous factor cache recorded virtually no hits and only slowed down the scan.
|
||||
|
||||
ulong stepHigh = step.High;
|
||||
ulong stepLow = step.Low;
|
||||
ulong limitHigh = limit.High;
|
||||
|
||||
if (stepHigh == 0UL && limitHigh == 0UL)
|
||||
{
|
||||
ulong maxK = allowedMax > 0UL ? (allowedMax - 1UL) / stepLow : 0UL;
|
||||
if (maxK == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong startK = minK < 1UL ? 1UL : minK;
|
||||
bool processedTop = true;
|
||||
bool processedBottom = true;
|
||||
bool composite = false;
|
||||
|
||||
if (startK <= maxK)
|
||||
{
|
||||
composite = CheckDivisors64Range(
|
||||
prime,
|
||||
stepLow,
|
||||
allowedMax,
|
||||
startK,
|
||||
maxK,
|
||||
ref currentK,
|
||||
out processedTop,
|
||||
out foundDivisor);
|
||||
if (composite)
|
||||
{
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ulong lowerEnd = startK > 1UL ? startK - 1UL : 0UL;
|
||||
if (lowerEnd >= 1UL && startK == 1UL)
|
||||
{
|
||||
lowerEnd = Math.Min(lowerEnd, maxK);
|
||||
currentK = 1UL;
|
||||
composite = CheckDivisors64Range(
|
||||
prime,
|
||||
stepLow,
|
||||
allowedMax,
|
||||
1UL,
|
||||
lowerEnd,
|
||||
ref currentK,
|
||||
out processedBottom,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
processedAll = processedTop && processedBottom;
|
||||
return composite;
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator gpu = Accelerator;
|
||||
var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor);
|
||||
|
||||
while (divisor.CompareTo(limit) <= 0)
|
||||
{
|
||||
if (residueStepper.IsAdmissible())
|
||||
{
|
||||
ulong candidate = divisor.Low;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||
ulong divisorCycle;
|
||||
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
|
||||
candidate,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
|
||||
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
|
||||
candidate,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
RecordState(currentK);
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
|
||||
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
|
||||
foundDivisor = candidate;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
divisor.Add(step);
|
||||
currentK++;
|
||||
residueStepper.Advance();
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivisorsLarge(
|
||||
ulong prime,
|
||||
BigInteger allowedMax,
|
||||
BigInteger minK,
|
||||
BigInteger step,
|
||||
out bool processedAll,
|
||||
out BigInteger foundDivisor)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = BigInteger.Zero;
|
||||
BigInteger currentK = minK < BigInteger.One ? BigInteger.One : minK;
|
||||
BigInteger divisor = (step * currentK) + BigInteger.One;
|
||||
if (allowedMax.IsZero)
|
||||
{
|
||||
processedAll = false;
|
||||
return false;
|
||||
}
|
||||
if (divisor > allowedMax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
|
||||
|
||||
var rem10 = new BigIntegerCycleRemainderStepper(10);
|
||||
var rem8 = new BigIntegerCycleRemainderStepper(8);
|
||||
var rem5 = new BigIntegerCycleRemainderStepper(5);
|
||||
var rem3 = new BigIntegerCycleRemainderStepper(3);
|
||||
var rem7 = new BigIntegerCycleRemainderStepper(7);
|
||||
var rem11 = new BigIntegerCycleRemainderStepper(11);
|
||||
var rem13 = new BigIntegerCycleRemainderStepper(13);
|
||||
var rem17 = new BigIntegerCycleRemainderStepper(17);
|
||||
var rem19 = new BigIntegerCycleRemainderStepper(19);
|
||||
|
||||
byte remainder10 = (byte)rem10.Initialize(divisor);
|
||||
byte remainder8 = (byte)rem8.Initialize(divisor);
|
||||
byte remainder5 = (byte)rem5.Initialize(divisor);
|
||||
byte remainder3 = (byte)rem3.Initialize(divisor);
|
||||
byte remainder7 = (byte)rem7.Initialize(divisor);
|
||||
byte remainder11 = (byte)rem11.Initialize(divisor);
|
||||
byte remainder13 = (byte)rem13.Initialize(divisor);
|
||||
byte remainder17 = (byte)rem17.Initialize(divisor);
|
||||
byte remainder19 = (byte)rem19.Initialize(divisor);
|
||||
|
||||
while (divisor <= allowedMax)
|
||||
{
|
||||
bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0 && remainder13 != 0 && remainder17 != 0 && remainder19 != 0;
|
||||
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
|
||||
{
|
||||
if (MersenneNumberDivisorByDivisorTester.IsProbablePrimeBigInteger(divisor))
|
||||
{
|
||||
BigInteger powResult = BigInteger.ModPow(2, prime, divisor);
|
||||
RecordState(currentK);
|
||||
if (powResult.IsOne)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RecordState(currentK);
|
||||
}
|
||||
}
|
||||
|
||||
currentK += BigInteger.One;
|
||||
divisor += step;
|
||||
remainder10 = (byte)rem10.ComputeNext(divisor);
|
||||
remainder8 = (byte)rem8.ComputeNext(divisor);
|
||||
remainder5 = (byte)rem5.ComputeNext(divisor);
|
||||
remainder3 = (byte)rem3.ComputeNext(divisor);
|
||||
remainder7 = (byte)rem7.ComputeNext(divisor);
|
||||
remainder11 = (byte)rem11.ComputeNext(divisor);
|
||||
remainder13 = (byte)rem13.ComputeNext(divisor);
|
||||
remainder17 = (byte)rem17.ComputeNext(divisor);
|
||||
remainder19 = (byte)rem19.ComputeNext(divisor);
|
||||
|
||||
if (divisor > allowedMax)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = BigInteger.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckDivisors64Range(
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong allowedMax,
|
||||
ulong startK,
|
||||
ulong endK,
|
||||
ref ulong currentK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
if (startK < 1UL || endK < startK)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
currentK = startK;
|
||||
|
||||
UInt128 step128 = step;
|
||||
UInt128 allowedMax128 = allowedMax;
|
||||
UInt128 startDivisor128 = (step128 * startK) + UInt128.One;
|
||||
if (startDivisor128 > allowedMax128)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt128 rangeLimit128 = (step128 * endK) + UInt128.One;
|
||||
if (rangeLimit128 > allowedMax128)
|
||||
{
|
||||
rangeLimit128 = allowedMax128;
|
||||
}
|
||||
|
||||
ulong startDivisor = (ulong)startDivisor128;
|
||||
ulong rangeLimit = (ulong)rangeLimit128;
|
||||
|
||||
var residueStepper = new MersenneDivisorResidueStepper(prime, (GpuUInt128)step128, (GpuUInt128)startDivisor128);
|
||||
|
||||
return CheckDivisors64(
|
||||
prime,
|
||||
step,
|
||||
rangeLimit,
|
||||
startDivisor,
|
||||
ref residueStepper,
|
||||
ref currentK,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
private bool CheckDivisors64(
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong limit,
|
||||
ulong divisor,
|
||||
ref MersenneDivisorResidueStepper residueStepper,
|
||||
ref ulong currentK,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator gpu = Accelerator;
|
||||
bool canAdvance = step <= limit;
|
||||
if (!canAdvance)
|
||||
{
|
||||
if (divisor <= limit && residueStepper.IsAdmissible())
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
|
||||
divisor,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
|
||||
divisor,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor > limit)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong remainingIterations = ((limit - divisor) / step) + 1UL;
|
||||
while (true)
|
||||
{
|
||||
if (residueStepper.IsAdmissible())
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!MersenneNumberDivisorByDivisorTester.TryCalculateCycleLengthForExponentHybrid(
|
||||
divisor,
|
||||
gpu,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = MersenneNumberDivisorByDivisorTester.CalculateCycleLengthHybrid(
|
||||
divisor,
|
||||
gpu,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
RecordState(currentK);
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
remainingIterations--;
|
||||
if (remainingIterations == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisor += step;
|
||||
currentK++;
|
||||
residueStepper.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
private void RecordState(BigInteger k)
|
||||
{
|
||||
string path = StateFilePath;
|
||||
|
||||
if (k <= _lastSavedK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int next = _stateCounter + 1;
|
||||
if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval)
|
||||
{
|
||||
string? directory = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
File.AppendAllText(path, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine);
|
||||
_stateCounter = 0;
|
||||
_lastSavedK = k;
|
||||
}
|
||||
else
|
||||
{
|
||||
_stateCounter = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithCpuOrder(
|
||||
in MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder prototype,
|
||||
Action markComposite,
|
||||
Action clearComposite,
|
||||
Action<ulong, bool, bool, bool, BigInteger> printResult)
|
||||
{
|
||||
private MersenneNumberDivisorByDivisorCpuTesterWithCpuOrder _tester = new()
|
||||
{
|
||||
DivisorLimit = prototype.DivisorLimit,
|
||||
MinK = EnvironmentConfiguration.MinK,
|
||||
};
|
||||
|
||||
private MersenneCpuDivisorScanSessionWithCpuOrder _divisorScanSession = new();
|
||||
|
||||
private readonly Action _markComposite = markComposite;
|
||||
private readonly Action _clearComposite = clearComposite;
|
||||
private readonly Action<ulong, bool, bool, bool, BigInteger> _printResult = printResult;
|
||||
|
||||
public void ProcessPrime(ulong prime)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"Processing {prime}");
|
||||
string stateFile = Path.Combine(
|
||||
PerfectNumberConstants.ByDivisorStateDirectory,
|
||||
prime.ToString(CultureInfo.InvariantCulture) + ".bin");
|
||||
|
||||
ConfigureForPrime(stateFile);
|
||||
|
||||
bool isPrime = _tester.IsPrime(prime, out bool divisorsExhausted, out BigInteger divisor);
|
||||
|
||||
if (!isPrime)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile))
|
||||
{
|
||||
File.Delete(stateFile);
|
||||
}
|
||||
|
||||
_markComposite();
|
||||
_printResult(prime, true, true, false, divisor);
|
||||
Console.WriteLine($"Finished processing {prime}");
|
||||
return;
|
||||
}
|
||||
|
||||
_clearComposite();
|
||||
_printResult(prime, true, divisorsExhausted, true, divisor);
|
||||
Console.WriteLine($"Finished processing {prime}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error processing {ex.Message} {ex.StackTrace}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureForPrime(string stateFile)
|
||||
{
|
||||
BigInteger resumeK = EnvironmentConfiguration.MinK;
|
||||
if (File.Exists(stateFile))
|
||||
{
|
||||
if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK))
|
||||
{
|
||||
resumeK = lastK + BigInteger.One;
|
||||
_tester.ResumeFromState(stateFile, lastK, resumeK);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
|
||||
}
|
||||
|
||||
_tester.ResetStateTracking();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithGpuOrder
|
||||
{
|
||||
private MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder _tester;
|
||||
private MersenneCpuDivisorScanSessionWithGpuOrder _divisorScanSession;
|
||||
|
||||
private readonly Action _markComposite;
|
||||
private readonly Action _clearComposite;
|
||||
private readonly Action<ulong, bool, bool, bool, BigInteger> _printResult;
|
||||
|
||||
public MersenneNumberDivisorByDivisorPrimeScanSessionWithGpuOrder(
|
||||
in MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder prototype,
|
||||
Action markComposite,
|
||||
Action clearComposite,
|
||||
Action<ulong, bool, bool, bool, BigInteger> printResult)
|
||||
{
|
||||
_tester = new MersenneNumberDivisorByDivisorCpuTesterWithGpuOrder
|
||||
{
|
||||
DivisorLimit = prototype.DivisorLimit,
|
||||
MinK = EnvironmentConfiguration.MinK,
|
||||
};
|
||||
|
||||
_divisorScanSession = new MersenneCpuDivisorScanSessionWithGpuOrder();
|
||||
_divisorScanSession.Configure(_tester.Accelerator);
|
||||
|
||||
_markComposite = markComposite;
|
||||
_clearComposite = clearComposite;
|
||||
_printResult = printResult;
|
||||
}
|
||||
|
||||
public void ProcessPrime(ulong prime)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"Processing {prime}");
|
||||
string stateFile = Path.Combine(
|
||||
PerfectNumberConstants.ByDivisorStateDirectory,
|
||||
prime.ToString(CultureInfo.InvariantCulture) + ".bin");
|
||||
|
||||
ConfigureForPrime(stateFile);
|
||||
|
||||
bool isPrime = _tester.IsPrime(prime, out bool divisorsExhausted, out BigInteger divisor);
|
||||
|
||||
if (!isPrime)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile))
|
||||
{
|
||||
File.Delete(stateFile);
|
||||
}
|
||||
|
||||
_markComposite();
|
||||
_printResult(prime, true, true, false, divisor);
|
||||
Console.WriteLine($"Finished processing {prime}");
|
||||
return;
|
||||
}
|
||||
|
||||
_clearComposite();
|
||||
_printResult(prime, true, divisorsExhausted, true, divisor);
|
||||
Console.WriteLine($"Finished processing {prime}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error processing {ex.Message} {ex.StackTrace}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureForPrime(string stateFile)
|
||||
{
|
||||
BigInteger resumeK = EnvironmentConfiguration.MinK;
|
||||
if (File.Exists(stateFile))
|
||||
{
|
||||
if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK))
|
||||
{
|
||||
resumeK = lastK + BigInteger.One;
|
||||
_tester.ResumeFromState(stateFile, lastK, resumeK);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
|
||||
}
|
||||
|
||||
_tester.ResetStateTracking();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(_tester.Accelerator);
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
internal struct MersenneNumberDivisorByDivisorPrimeScanSessionWithHybridOrder
|
||||
{
|
||||
private MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder _tester;
|
||||
private MersenneCpuDivisorScanSessionWithHybridOrder _divisorScanSession;
|
||||
|
||||
private readonly Action _markComposite;
|
||||
private readonly Action _clearComposite;
|
||||
private readonly Action<ulong, bool, bool, bool, BigInteger> _printResult;
|
||||
|
||||
public MersenneNumberDivisorByDivisorPrimeScanSessionWithHybridOrder(
|
||||
in MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder prototype,
|
||||
Action markComposite,
|
||||
Action clearComposite,
|
||||
Action<ulong, bool, bool, bool, BigInteger> printResult)
|
||||
{
|
||||
_tester = new MersenneNumberDivisorByDivisorCpuTesterWithHybridOrder
|
||||
{
|
||||
DivisorLimit = prototype.DivisorLimit,
|
||||
MinK = EnvironmentConfiguration.MinK,
|
||||
};
|
||||
|
||||
_divisorScanSession = new MersenneCpuDivisorScanSessionWithHybridOrder();
|
||||
_divisorScanSession.Configure(_tester.Accelerator);
|
||||
|
||||
_markComposite = markComposite;
|
||||
_clearComposite = clearComposite;
|
||||
_printResult = printResult;
|
||||
}
|
||||
|
||||
public void ProcessPrime(ulong prime)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"Processing {prime}");
|
||||
string stateFile = Path.Combine(
|
||||
PerfectNumberConstants.ByDivisorStateDirectory,
|
||||
prime.ToString(CultureInfo.InvariantCulture) + ".bin");
|
||||
|
||||
ConfigureForPrime(stateFile);
|
||||
|
||||
bool isPrime = _tester.IsPrime(prime, out bool divisorsExhausted, out BigInteger divisor);
|
||||
|
||||
if (!isPrime)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile))
|
||||
{
|
||||
File.Delete(stateFile);
|
||||
}
|
||||
|
||||
_markComposite();
|
||||
_printResult(prime, true, true, false, divisor);
|
||||
Console.WriteLine($"Finished processing {prime}");
|
||||
return;
|
||||
}
|
||||
|
||||
_clearComposite();
|
||||
_printResult(prime, true, divisorsExhausted, true, divisor);
|
||||
Console.WriteLine($"Finished processing {prime}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error processing {ex.Message} {ex.StackTrace}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureForPrime(string stateFile)
|
||||
{
|
||||
BigInteger resumeK = EnvironmentConfiguration.MinK;
|
||||
if (File.Exists(stateFile))
|
||||
{
|
||||
if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK))
|
||||
{
|
||||
resumeK = lastK + BigInteger.One;
|
||||
_tester.ResumeFromState(stateFile, lastK, resumeK);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
|
||||
}
|
||||
|
||||
_tester.ResetStateTracking();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
PrimeOrderCalculatorAccelerator.Return(_tester.Accelerator);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace PerfectNumbers.Core;
|
||||
|
||||
public struct CycleRemainderStepper(ulong cycleLength)
|
||||
{
|
||||
private readonly ulong _cycleLength = cycleLength;
|
||||
public ulong CycleLength = cycleLength;
|
||||
private ulong _previousPrime = 0UL;
|
||||
private ulong _currentRemainder = 0UL;
|
||||
|
||||
@@ -18,7 +18,7 @@ public struct CycleRemainderStepper(ulong cycleLength)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong Initialize(ulong prime)
|
||||
{
|
||||
ulong cycleLength = _cycleLength;
|
||||
ulong cycleLength = CycleLength;
|
||||
ulong remainder = prime.ReduceCycleRemainder(cycleLength);
|
||||
|
||||
_previousPrime = prime;
|
||||
@@ -29,7 +29,7 @@ public struct CycleRemainderStepper(ulong cycleLength)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong ComputeNext(ulong prime)
|
||||
{
|
||||
ulong cycleLength = _cycleLength;
|
||||
ulong cycleLength = CycleLength;
|
||||
ulong delta = prime - _previousPrime;
|
||||
_previousPrime = prime;
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
#pragma warning disable CA2211 // The fields are settable on purpose for the best performance
|
||||
|
||||
public static class EnvironmentConfiguration
|
||||
{
|
||||
public static ulong DivisorLimit = ulong.MaxValue;
|
||||
public static int GpuBatchSize = GpuConstants.ScanBatchSize;
|
||||
public static int GpuRatio = PerfectNumberConstants.GpuRatio;
|
||||
public static BigInteger MinK = BigInteger.One;
|
||||
public static ComputationDevice OrderDevice = ComputationDevice.Cpu;
|
||||
public static int RollingAccelerators = PerfectNumberConstants.RollingAccelerators;
|
||||
public static string? StateFilePath;
|
||||
public static bool UseGpuOrder;
|
||||
public static bool UseHybridOrder;
|
||||
public static bool UseCpuOrder;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static void Initialize()
|
||||
{
|
||||
UseGpuOrder = OrderDevice == ComputationDevice.Gpu;
|
||||
UseHybridOrder = OrderDevice == ComputationDevice.Hybrid;
|
||||
UseCpuOrder = OrderDevice == ComputationDevice.Cpu;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CA2211 // Non-constant fields should not be visible
|
||||
@@ -48,7 +48,7 @@ namespace PerfectNumbers.Core.Gpu
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly AcceleratorPool Shared = new(PerfectNumberConstants.RollingAccelerators);
|
||||
public static readonly AcceleratorPool Shared = new(EnvironmentConfiguration.RollingAccelerators);
|
||||
|
||||
private readonly int Capacity;
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
public static class AcceleratorStreamPool
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
private static readonly PollingSemaphore[] _locks = new PollingSemaphore[PerfectNumberConstants.RollingAccelerators];
|
||||
private static readonly ConcurrentFixedCapacityStack<AcceleratorStream>[] _streams = new ConcurrentFixedCapacityStack<AcceleratorStream>[PerfectNumberConstants.RollingAccelerators];
|
||||
private static readonly PollingSemaphore[] _locks = new PollingSemaphore[EnvironmentConfiguration.RollingAccelerators];
|
||||
private static readonly ConcurrentFixedCapacityStack<AcceleratorStream>[] _streams = new ConcurrentFixedCapacityStack<AcceleratorStream>[EnvironmentConfiguration.RollingAccelerators];
|
||||
|
||||
private static PollingSemaphore CreateLock() => new(PerfectNumberConstants.ThreadsByAccelerator, TimeSpan.FromMilliseconds(1000), 1);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ internal readonly struct HeuristicCombinedGpuTables
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
private static readonly HeuristicCombinedGpuTables[] _sharedTables = new HeuristicCombinedGpuTables[PerfectNumberConstants.RollingAccelerators];
|
||||
private static readonly HeuristicCombinedGpuTables[] _sharedTables = new HeuristicCombinedGpuTables[EnvironmentConfiguration.RollingAccelerators];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new(_accelerators[acceleratorIndex], stream);
|
||||
|
||||
@@ -7,9 +7,9 @@ internal sealed class HeuristicGroupABGpuTables
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
private static readonly HeuristicGroupABGpuTables[] _sharedTables = new HeuristicGroupABGpuTables[PerfectNumberConstants.RollingAccelerators];
|
||||
private static readonly HeuristicGroupABGpuTables[] _sharedTables = new HeuristicGroupABGpuTables[EnvironmentConfiguration.RollingAccelerators];
|
||||
|
||||
private static readonly SemaphoreSlim[] _locks = [..Enumerable.Range(1, PerfectNumberConstants.RollingAccelerators).Select(_ => new SemaphoreSlim(1))];
|
||||
private static readonly SemaphoreSlim[] _locks = [..Enumerable.Range(1, EnvironmentConfiguration.RollingAccelerators).Select(_ => new SemaphoreSlim(1))];
|
||||
|
||||
internal static HeuristicGroupABGpuTables EnsureStaticTables(int acceleratorIndex, AcceleratorStream stream)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
internal readonly struct LastDigitGpuTables
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
private static readonly LastDigitGpuTables[] _sharedTables = new LastDigitGpuTables[PerfectNumberConstants.RollingAccelerators];
|
||||
private static readonly LastDigitGpuTables[] _sharedTables = new LastDigitGpuTables[EnvironmentConfiguration.RollingAccelerators];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static LastDigitGpuTables GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
|
||||
|
||||
@@ -9,6 +9,8 @@ public static class GpuPrimeWorkLimiter
|
||||
public static void Acquire() => _semaphore.Wait();
|
||||
public static void Release() => _semaphore.Release();
|
||||
|
||||
public static int CurrentLimit => _currentLimit;
|
||||
|
||||
public static void SetLimit(int value)
|
||||
{
|
||||
if (value <= 0)
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
internal struct MersenneNumberDivisorByDivisorGpuPrimeScanSession
|
||||
{
|
||||
private readonly MersenneNumberDivisorByDivisorGpuTester _tester;
|
||||
private readonly ulong _divisorLimit;
|
||||
private readonly Action _markComposite;
|
||||
private readonly Action _clearComposite;
|
||||
private readonly Action<ulong, bool, bool, bool, BigInteger> _printResult;
|
||||
|
||||
public MersenneNumberDivisorByDivisorGpuPrimeScanSession(
|
||||
MersenneNumberDivisorByDivisorGpuTester prototype,
|
||||
Action markComposite,
|
||||
Action clearComposite,
|
||||
Action<ulong, bool, bool, bool, BigInteger> printResult)
|
||||
{
|
||||
_tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
MinK = EnvironmentConfiguration.MinK,
|
||||
};
|
||||
_divisorLimit = prototype.DivisorLimit;
|
||||
_tester.SetDivisorLimitForClone(_divisorLimit);
|
||||
|
||||
_markComposite = markComposite;
|
||||
_clearComposite = clearComposite;
|
||||
_printResult = printResult;
|
||||
}
|
||||
|
||||
public void ProcessPrime(ulong prime)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"Processing {prime}");
|
||||
string stateFile = Path.Combine(
|
||||
PerfectNumberConstants.ByDivisorStateDirectory,
|
||||
prime.ToString(CultureInfo.InvariantCulture) + ".bin");
|
||||
|
||||
ConfigureForPrime(stateFile);
|
||||
|
||||
bool isPrime = _tester.IsPrime(prime, out bool divisorsExhausted, out BigInteger divisor);
|
||||
|
||||
if (!isPrime)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile))
|
||||
{
|
||||
File.Delete(stateFile);
|
||||
}
|
||||
|
||||
_markComposite();
|
||||
_printResult(prime, true, true, false, divisor);
|
||||
Console.WriteLine($"Finished processing {prime}");
|
||||
return;
|
||||
}
|
||||
|
||||
_clearComposite();
|
||||
_printResult(prime, true, divisorsExhausted, true, divisor);
|
||||
Console.WriteLine($"Finished processing {prime}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error processing {ex.Message} {ex.StackTrace}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureForPrime(string stateFile)
|
||||
{
|
||||
BigInteger resumeK = EnvironmentConfiguration.MinK;
|
||||
if (File.Exists(stateFile))
|
||||
{
|
||||
if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK))
|
||||
{
|
||||
resumeK = lastK + BigInteger.One;
|
||||
_tester.ResumeFromState(stateFile, lastK, resumeK);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
|
||||
}
|
||||
|
||||
_tester.ResetStateTracking();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -14,67 +14,46 @@ namespace PerfectNumbers.Core.Gpu;
|
||||
/// </summary>
|
||||
public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
||||
// EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards,
|
||||
// so the synchronization fields from the previous implementation remain commented out here.
|
||||
private readonly PrimeOrderCalculatorAccelerator Accelerator = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
private ulong _divisorLimit;
|
||||
private BigInteger _minK = BigInteger.One;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal void SetDivisorLimitForClone(ulong divisorLimit) => _divisorLimit = divisorLimit;
|
||||
public BigInteger MinK = BigInteger.One;
|
||||
public string? StateFilePath;
|
||||
|
||||
private readonly ConcurrentFixedCapacityStack<MersenneNumberDivisorByDivisorAccelerator>[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentFixedCapacityStack<MersenneNumberDivisorByDivisorAccelerator>(PerfectNumberConstants.DefaultPoolCapacity))];
|
||||
|
||||
private readonly ConcurrentFixedCapacityStack<DivisorScanSession> _sessionPool = new(PerfectNumberConstants.DefaultPoolCapacity);
|
||||
|
||||
public int GpuBatchSize
|
||||
{
|
||||
get => _gpuBatchSize;
|
||||
set => _gpuBatchSize = value < 1 ? 1 : value;
|
||||
}
|
||||
|
||||
int IMersenneNumberDivisorByDivisorTester.BatchSize
|
||||
{
|
||||
get => GpuBatchSize;
|
||||
set => GpuBatchSize = value;
|
||||
}
|
||||
|
||||
public BigInteger MinK
|
||||
{
|
||||
get => _minK;
|
||||
set => _minK = value < BigInteger.One ? BigInteger.One : value;
|
||||
}
|
||||
|
||||
public string? StateFilePath { get; set; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void ResetStateTracking()
|
||||
{
|
||||
}
|
||||
|
||||
public void ResumeFromState(BigInteger lastSavedK)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
|
||||
{
|
||||
StateFilePath = stateFile;
|
||||
if (lastSavedK > ulong.MaxValue)
|
||||
{
|
||||
throw new NotSupportedException("GPU by-divisor tester does not support MinK above UInt64.");
|
||||
ThrowNotSupportedLastSavedK();
|
||||
return; // We'll never get to here
|
||||
}
|
||||
|
||||
_minK = lastSavedK + BigInteger.One;
|
||||
MinK = minK;
|
||||
}
|
||||
|
||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
// EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards,
|
||||
// so synchronization and runtime configuration guards are unnecessary here.
|
||||
private static void ThrowNotSupportedLastSavedK() => throw new NotSupportedException("GPU by-divisor tester does not support MinK above UInt64.");
|
||||
|
||||
_divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime);
|
||||
}
|
||||
|
||||
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
|
||||
{
|
||||
ulong allowedMax;
|
||||
int batchCapacity;
|
||||
|
||||
// EvenPerfectBitScanner only calls into this tester after configuring it once, so we can read the cached values without locking.
|
||||
|
||||
allowedMax = ComputeAllowedMaxDivisorGpu(prime, _divisorLimit);
|
||||
batchCapacity = _gpuBatchSize;
|
||||
|
||||
// Production scans never shrink the divisor window below three, so this guard stays commented out.
|
||||
// if (allowedMax < 3UL)
|
||||
@@ -84,17 +63,16 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
// }
|
||||
|
||||
bool composite;
|
||||
bool coveredRange;
|
||||
ulong lastProcessed;
|
||||
|
||||
// GpuPrimeWorkLimiter();
|
||||
// int acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
// var accelerator = _accelerators[acceleratorIndex];
|
||||
PrimeOrderCalculatorAccelerator gpu = Accelerator;
|
||||
var acceleratorIndex = gpu.AcceleratorIndex;
|
||||
|
||||
// Monitor.Enter(gpuLease.ExecutionLock);
|
||||
|
||||
var resources = RentBatchResources(acceleratorIndex, batchCapacity);
|
||||
var resources = RentBatchResources(acceleratorIndex, EnvironmentConfiguration.GpuBatchSize);
|
||||
|
||||
composite = CheckDivisors(
|
||||
gpu,
|
||||
@@ -115,8 +93,8 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
resources.Offsets,
|
||||
resources.Counts,
|
||||
resources.Cycles,
|
||||
out lastProcessed,
|
||||
out coveredRange);
|
||||
out ulong lastProcessed,
|
||||
out bool coveredRange);
|
||||
|
||||
ReturnBatchResources(acceleratorIndex, resources);
|
||||
// Monitor.Exit(gpuLease.ExecutionLock);
|
||||
@@ -134,17 +112,19 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private ulong GetMinKOrThrow()
|
||||
{
|
||||
if (_minK > ulong.MaxValue)
|
||||
if (MinK > ulong.MaxValue)
|
||||
{
|
||||
throw new NotSupportedException("GPU by-divisor tester does not support MinK above UInt64.");
|
||||
ThrowNotSupportedLastSavedK();
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
return (ulong)_minK;
|
||||
return (ulong)MinK;
|
||||
}
|
||||
|
||||
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
// The scanner always supplies matching spans, so the previous validation remains commented out.
|
||||
// if (allowedMaxValues.Length < primes.Length)
|
||||
@@ -165,7 +145,12 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
// divisorLimit = _divisorLimit;
|
||||
// }
|
||||
|
||||
divisorLimit = _divisorLimit;
|
||||
// EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards,
|
||||
// so synchronization and runtime configuration guards are unnecessary here.
|
||||
|
||||
divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime);
|
||||
|
||||
_divisorLimit = divisorLimit;
|
||||
|
||||
for (int index = 0; index < primes.Length; index++)
|
||||
{
|
||||
@@ -526,12 +511,12 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
private readonly Accelerator _accelerator;
|
||||
private readonly PrimeOrderCalculatorAccelerator _primeOrderCalculatorAccelerator;
|
||||
private readonly AcceleratorStream _stream;
|
||||
private Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
|
||||
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!;
|
||||
private MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!;
|
||||
private MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!;
|
||||
private MemoryBuffer1D<ulong, Stride1D.Dense> _cycleBuffer = null!;
|
||||
private MemoryBuffer1D<int, Stride1D.Dense> _firstHitBuffer = null!;
|
||||
private readonly Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
|
||||
private readonly MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!;
|
||||
private readonly MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!;
|
||||
private readonly MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!;
|
||||
private readonly MemoryBuffer1D<ulong, Stride1D.Dense> _cycleBuffer = null!;
|
||||
private readonly MemoryBuffer1D<int, Stride1D.Dense> _firstHitBuffer = null!;
|
||||
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentsBuffer = null!;
|
||||
private MemoryBuffer1D<byte, Stride1D.Dense> _hitBuffer = null!;
|
||||
private ulong[] _hostBuffer = null!;
|
||||
@@ -546,7 +531,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
Accelerator accelerator = gpu.Accelerator;
|
||||
_accelerator = accelerator;
|
||||
_stream = accelerator.CreateStream();
|
||||
_capacity = Math.Max(1, owner._gpuBatchSize);
|
||||
|
||||
// lock (accelerator)
|
||||
{
|
||||
@@ -559,11 +543,9 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
|
||||
int desiredCapacity = PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount;
|
||||
_capacity = desiredCapacity;
|
||||
// lock (accelerator)
|
||||
{
|
||||
_exponentsBuffer = accelerator.Allocate1D<ulong>(desiredCapacity);
|
||||
_hitBuffer = accelerator.Allocate1D<byte>(desiredCapacity);
|
||||
}
|
||||
|
||||
_exponentsBuffer = accelerator.Allocate1D<ulong>(desiredCapacity);
|
||||
_hitBuffer = accelerator.Allocate1D<byte>(desiredCapacity);
|
||||
|
||||
_hostBuffer = ThreadStaticPools.UlongPool.Rent(desiredCapacity);
|
||||
|
||||
@@ -729,23 +711,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||
{
|
||||
// The configuration is immutable after setup, so the cached limit can be read without locking.
|
||||
// lock (_sync)
|
||||
// {
|
||||
// if (!_isConfigured)
|
||||
// {
|
||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||
// }
|
||||
|
||||
// return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit);
|
||||
// }
|
||||
|
||||
return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ public readonly struct SmallPrimeFactorGpuTables
|
||||
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
private static readonly SmallPrimeFactorGpuTables?[] _sharedTables = new SmallPrimeFactorGpuTables?[PerfectNumberConstants.RollingAccelerators];
|
||||
private static readonly SmallPrimeFactorGpuTables?[] _sharedTables = new SmallPrimeFactorGpuTables?[EnvironmentConfiguration.RollingAccelerators];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static SmallPrimeFactorGpuTables? GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
|
||||
|
||||
@@ -5,32 +5,20 @@ namespace PerfectNumbers.Core;
|
||||
|
||||
public interface IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
int BatchSize { get; set; }
|
||||
|
||||
BigInteger MinK { get; set; }
|
||||
|
||||
void ConfigureFromMaxPrime(ulong maxPrime);
|
||||
|
||||
ulong DivisorLimit { get; }
|
||||
|
||||
ulong GetAllowedMaxDivisor(ulong prime);
|
||||
|
||||
bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out BigInteger divisor);
|
||||
bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor);
|
||||
|
||||
IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu);
|
||||
|
||||
string? StateFilePath { get; set; }
|
||||
|
||||
void ResetStateTracking();
|
||||
|
||||
void ResumeFromState(BigInteger lastSavedK);
|
||||
void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK);
|
||||
|
||||
void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
|
||||
void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
|
||||
|
||||
public interface IDivisorScanSession
|
||||
{
|
||||
bool CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes);
|
||||
|
||||
void Return();
|
||||
// void Return();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -12,14 +12,14 @@ public static class PerfectNumberConstants
|
||||
public const int DefaultThreadPoolCapacity = 30_000;
|
||||
public const int DefaultSpecialMaxFactorCapacity = 1024;
|
||||
public const ulong ExtraDivisorCycleSearchLimit = 64UL;
|
||||
public static int GpuRatio = 16384 - 1;
|
||||
public const int GpuRatio = 16384 - 1;
|
||||
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;
|
||||
public const int DefaultStringBuilderCapacity = 10_240;
|
||||
public const int MaxOddPowersCount = 128;
|
||||
public const int PooledArrayThreshold = 64;
|
||||
public const int Pow2WindowSize = 8;
|
||||
public static int RollingAccelerators = 298; //SharedGpuContext.Device.MaxNumThreadsPerGroup;
|
||||
public const int RollingAccelerators = 298;
|
||||
public const int ThreadsByAccelerator = 1;
|
||||
public const int ByDivisorStateSaveInterval = 200_000;
|
||||
public const string ByDivisorStateDirectory = "Checks";
|
||||
|
||||
@@ -328,12 +328,13 @@ internal static partial class PrimeOrderCalculator
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private static bool RunOnCpu()
|
||||
{
|
||||
if (PerfectNumberConstants.GpuRatio == 0)
|
||||
int gpuRatio = EnvironmentConfiguration.GpuRatio;
|
||||
if (gpuRatio == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PerfectNumberConstants.GpuRatio == 1)
|
||||
if (gpuRatio == 1)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
@@ -352,16 +353,16 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
|
||||
int cpuCount = Interlocked.Increment(ref _cpuCount);
|
||||
if (cpuCount == PerfectNumberConstants.GpuRatio)
|
||||
if (cpuCount == gpuRatio)
|
||||
{
|
||||
Interlocked.Add(ref _cpuCount, -PerfectNumberConstants.GpuRatio);
|
||||
Interlocked.Add(ref _cpuCount, -gpuRatio);
|
||||
}
|
||||
else if (cpuCount > PerfectNumberConstants.GpuRatio)
|
||||
else if (cpuCount > gpuRatio)
|
||||
{
|
||||
cpuCount -= PerfectNumberConstants.GpuRatio;
|
||||
cpuCount -= gpuRatio;
|
||||
}
|
||||
|
||||
return cpuCount != PerfectNumberConstants.GpuRatio;
|
||||
return cpuCount != gpuRatio;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
|
||||
@@ -153,9 +153,9 @@ internal sealed class TaskThreadPool
|
||||
return false;
|
||||
}
|
||||
|
||||
workItem = _pendingTaskQueue.Pop();
|
||||
if (workItem is { })
|
||||
if (_pendingTaskQueue.TryPop(out WorkItem dequeued))
|
||||
{
|
||||
workItem = dequeued;
|
||||
Interlocked.Decrement(ref _pendingTaskCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -127,24 +127,24 @@ namespace PerfectNumbers.Core
|
||||
get => _gpuUInt128Pool ??= ArrayPool<GpuUInt128>.Create();
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession;
|
||||
// [ThreadStatic]
|
||||
// private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static MersenneCpuDivisorScanSession RentMersenneCpuDivisorSession(Gpu.Accelerators.PrimeOrderCalculatorAccelerator gpu, ComputationDevice orderDevice)
|
||||
{
|
||||
if (_mersenneCpuDivisorSession is { } session)
|
||||
{
|
||||
session.Configure(gpu, orderDevice);
|
||||
_mersenneCpuDivisorSession = null;
|
||||
return session;
|
||||
}
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
// internal static MersenneCpuDivisorScanSession RentMersenneCpuDivisorSession(Gpu.Accelerators.PrimeOrderCalculatorAccelerator gpu, ComputationDevice orderDevice)
|
||||
// {
|
||||
// if (_mersenneCpuDivisorSession is { } session)
|
||||
// {
|
||||
// session.Configure(gpu, orderDevice);
|
||||
// _mersenneCpuDivisorSession = null;
|
||||
// return session;
|
||||
// }
|
||||
|
||||
return new MersenneCpuDivisorScanSession(gpu, orderDevice);
|
||||
}
|
||||
// return new MersenneCpuDivisorScanSession(gpu, orderDevice);
|
||||
// }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void ReturnMersenneCpuDivisorSession(MersenneCpuDivisorScanSession session) => _mersenneCpuDivisorSession = session;
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
// internal static void ReturnMersenneCpuDivisorSession(MersenneCpuDivisorScanSession session) => _mersenneCpuDivisorSession = session;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ArrayPool<UInt128>? _uint128Pool;
|
||||
|
||||
Reference in new issue
Block a user