Merge pull request #167 from mlemanczyk/codex/analyze-if-conditions-in-evenperfectbitscanner
Document unreachable guards in GPU by-divisor pipeline
This commit is contained in:
7 files changed
+292
-255
No files matched your search
@@ -3,6 +3,7 @@ using System.Buffers;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
@@ -10,14 +11,11 @@ namespace PerfectNumbers.Core.Cpu;
|
|||||||
|
|
||||||
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
||||||
{
|
{
|
||||||
private readonly object _sync = new();
|
|
||||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
||||||
private ulong _divisorLimit;
|
private ulong _divisorLimit;
|
||||||
private ulong _lastStatusDivisor;
|
private int _lastStatusDivisor;
|
||||||
private bool _isConfigured;
|
private bool _isConfigured;
|
||||||
private int _batchSize = 1_024;
|
private int _batchSize = 1_024;
|
||||||
|
|
||||||
private ulong _configuredMaxPrime;
|
|
||||||
private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu;
|
private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu;
|
||||||
private ByDivisorMontgomeryDevice _montgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
private ByDivisorMontgomeryDevice _montgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
||||||
|
|
||||||
@@ -34,79 +32,60 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
public ByDivisorDeltasDevice DeltasDevice
|
public ByDivisorDeltasDevice DeltasDevice
|
||||||
{
|
{
|
||||||
get => _deltasDevice;
|
get => _deltasDevice;
|
||||||
set
|
set => _deltasDevice = value;
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_deltasDevice = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByDivisorMontgomeryDevice MontgomeryDevice
|
public ByDivisorMontgomeryDevice MontgomeryDevice
|
||||||
{
|
{
|
||||||
get => _montgomeryDevice;
|
get => _montgomeryDevice;
|
||||||
set
|
set => _montgomeryDevice = value;
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_montgomeryDevice = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||||
{
|
_lastStatusDivisor = 0;
|
||||||
_configuredMaxPrime = maxPrime;
|
_isConfigured = true;
|
||||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
|
||||||
_lastStatusDivisor = 0UL;
|
|
||||||
_isConfigured = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (_sync)
|
// The by-divisor driver configures the tester during startup, so exposing the divisor limit never
|
||||||
{
|
// re-enters this method before initialization completes.
|
||||||
if (!_isConfigured)
|
// if (!_isConfigured)
|
||||||
{
|
// {
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
}
|
// }
|
||||||
|
|
||||||
return _divisorLimit;
|
return _divisorLimit;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
// The CLI wires the tester through ConfigureFromMaxPrime before invoking query helpers, so the guard
|
||||||
{
|
// would never trigger in production runs.
|
||||||
if (!_isConfigured)
|
// if (!_isConfigured)
|
||||||
{
|
// {
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
}
|
// }
|
||||||
|
|
||||||
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
||||||
{
|
{
|
||||||
ulong allowedMax;
|
// The tester is configured once during startup, so runtime IsPrime calls always observe a ready
|
||||||
lock (_sync)
|
// configuration and the legacy guard can remain commented out.
|
||||||
{
|
// if (!_isConfigured)
|
||||||
if (!_isConfigured)
|
// {
|
||||||
{
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
ulong divisorLimit = _divisorLimit;
|
||||||
}
|
ulong allowedMax = ComputeAllowedMaxDivisor(prime, divisorLimit);
|
||||||
|
|
||||||
if (allowedMax < 3UL)
|
if (allowedMax < 3UL)
|
||||||
{
|
{
|
||||||
@@ -128,10 +107,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
if (processedCount > 0UL)
|
if (processedCount > 0UL)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
UpdateStatusUnsafe(processedCount);
|
||||||
{
|
|
||||||
UpdateStatusUnsafe(processedCount);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (composite)
|
if (composite)
|
||||||
@@ -146,22 +122,21 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||||
{
|
{
|
||||||
if (allowedMaxValues.Length < primes.Length)
|
// EvenPerfectBitScanner always provides equally sized spans here, so the historical guard never triggers.
|
||||||
{
|
// if (allowedMaxValues.Length < primes.Length)
|
||||||
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
// {
|
||||||
}
|
// throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
||||||
|
// }
|
||||||
|
|
||||||
ulong divisorLimit;
|
// PrepareCandidates is scheduled only after ConfigureFromMaxPrime succeeds, so the tester state is
|
||||||
|
// stable here and the defensive guard is no longer needed.
|
||||||
|
// if (!_isConfigured)
|
||||||
|
// {
|
||||||
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
|
// }
|
||||||
|
|
||||||
lock (_sync)
|
// Configuration is immutable after startup, so reading the cached limit does not require locking.
|
||||||
{
|
ulong divisorLimit = _divisorLimit;
|
||||||
if (!_isConfigured)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
divisorLimit = _divisorLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int index = 0; index < primes.Length; index++)
|
for (int index = 0; index < primes.Length; index++)
|
||||||
{
|
{
|
||||||
@@ -171,21 +146,20 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||||
{
|
{
|
||||||
lock (_sync)
|
// The session pool is populated only after configuration, so consumers never request a session before
|
||||||
|
// ConfigureFromMaxPrime completes.
|
||||||
|
// if (!_isConfigured)
|
||||||
|
// {
|
||||||
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
||||||
{
|
{
|
||||||
if (!_isConfigured)
|
session.Reset();
|
||||||
{
|
return session;
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
|
||||||
{
|
|
||||||
session.Reset();
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DivisorScanSession(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new DivisorScanSession(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReturnSession(DivisorScanSession session)
|
private void ReturnSession(DivisorScanSession session)
|
||||||
@@ -236,11 +210,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
UInt128 step = (UInt128)prime << 1;
|
UInt128 step = (UInt128)prime << 1;
|
||||||
if (step == UInt128.Zero)
|
// EvenPerfectBitScanner only supplies primes greater than one here, so the shifted step never becomes zero.
|
||||||
{
|
// if (step == UInt128.Zero)
|
||||||
processedAll = true;
|
// {
|
||||||
return false;
|
// processedAll = true;
|
||||||
}
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
UInt128 limit = allowedMax;
|
UInt128 limit = allowedMax;
|
||||||
UInt128 divisor = step + UInt128.One;
|
UInt128 divisor = step + UInt128.One;
|
||||||
@@ -323,6 +298,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
if (divisorCycle == 0UL)
|
if (divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
|
// Keep this guard active to surface mismatches between generated candidates and the snapshot cache.
|
||||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,11 +345,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
GpuUInt128 step128 = new GpuUInt128(prime);
|
GpuUInt128 step128 = new GpuUInt128(prime);
|
||||||
step128.ShiftLeft(1);
|
step128.ShiftLeft(1);
|
||||||
if (step128.IsZero)
|
// Primes above one yield a non-zero 2p value even after widening to GpuUInt128, so the defensive
|
||||||
{
|
// zero guard remains commented out to document the invariant without branching.
|
||||||
processedAll = true;
|
// if (step128.IsZero)
|
||||||
return false;
|
// {
|
||||||
}
|
// processedAll = true;
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
GpuUInt128 limit128 = new GpuUInt128(allowedMax);
|
GpuUInt128 limit128 = new GpuUInt128(allowedMax);
|
||||||
GpuUInt128 divisor128 = step128;
|
GpuUInt128 divisor128 = step128;
|
||||||
@@ -449,10 +427,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
int chunkCount = remainingCandidates > (ulong)batchCapacity
|
int chunkCount = remainingCandidates > (ulong)batchCapacity
|
||||||
? batchCapacity
|
? batchCapacity
|
||||||
: (int)remainingCandidates;
|
: (int)remainingCandidates;
|
||||||
if (chunkCount <= 0)
|
// batchCapacity and remainingCandidates are strictly positive in this path, so the zero-count
|
||||||
{
|
// shortcut never executes on production scans.
|
||||||
break;
|
// if (chunkCount <= 0)
|
||||||
}
|
// {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
Span<ulong> candidateSpan = candidateBuffer.AsSpan(0, chunkCount);
|
Span<ulong> candidateSpan = candidateBuffer.AsSpan(0, chunkCount);
|
||||||
Span<byte> maskSpan = maskBuffer.AsSpan(0, chunkCount);
|
Span<byte> maskSpan = maskBuffer.AsSpan(0, chunkCount);
|
||||||
@@ -603,17 +583,34 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong interval = PerfectNumberConstants.ConsoleInterval;
|
int interval = PerfectNumberConstants.ConsoleInterval;
|
||||||
if (interval == 0UL)
|
// PerfectNumberConstants.ConsoleInterval is a positive constant, so this guard never triggers on EvenPerfectBitScanner.
|
||||||
|
// if (interval <= 0)
|
||||||
|
// {
|
||||||
|
// Volatile.Write(ref _lastStatusDivisor, 0);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
int processedDelta = (int)(processedCount % (uint)interval);
|
||||||
|
if (processedDelta == 0)
|
||||||
{
|
{
|
||||||
_lastStatusDivisor = 0UL;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong total = _lastStatusDivisor + processedCount;
|
while (true)
|
||||||
// TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
{
|
||||||
// status benchmarks so we avoid `%` in this hot loop while still wrapping progress correctly.
|
int previous = Volatile.Read(ref _lastStatusDivisor);
|
||||||
_lastStatusDivisor = total % interval;
|
int total = previous + processedDelta;
|
||||||
|
if (total >= interval)
|
||||||
|
{
|
||||||
|
total -= interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous == Interlocked.CompareExchange(ref _lastStatusDivisor, total, previous))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
||||||
@@ -696,11 +693,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong modulus = effectiveDivisorData.Modulus;
|
ulong modulus = effectiveDivisorData.Modulus;
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
// EvenPerfectBitScanner only schedules odd divisors greater than one, and the fallback
|
||||||
{
|
// above rebuilds Montgomery data when the modulus mismatches, so this guard never
|
||||||
hits.Clear();
|
// triggers for production scans.
|
||||||
return;
|
// if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
}
|
// {
|
||||||
|
// hits.Clear();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
if (divisorCycle == 0UL)
|
if (divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
@@ -803,10 +803,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private void EnsureCapacity(int requiredLength)
|
private void EnsureCapacity(int requiredLength)
|
||||||
{
|
{
|
||||||
if (requiredLength <= 0)
|
// Prime batches are never empty on the by-divisor scanner path, so the zero-length guard stays commented
|
||||||
{
|
// out to remove the redundant branch.
|
||||||
return;
|
// if (requiredLength <= 0)
|
||||||
}
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
// Sessions release pooled buffers between scans; a single null check covers every array because buffers are rented and returned together.
|
// Sessions release pooled buffers between scans; a single null check covers every array because buffers are rented and returned together.
|
||||||
if (requiredLength <= _bufferCapacity && _primeDeltas is not null)
|
if (requiredLength <= _bufferCapacity && _primeDeltas is not null)
|
||||||
@@ -817,7 +819,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
int newCapacity = _bufferCapacity;
|
int newCapacity = _bufferCapacity;
|
||||||
if (newCapacity == 0)
|
if (newCapacity == 0)
|
||||||
{
|
{
|
||||||
// Fresh sessions start without buffers; seed the first rent with a single-slot array.
|
// EvenPerfectBitScanner hits this branch on the first batch of each session, so keep the seed allocation.
|
||||||
newCapacity = 1;
|
newCapacity = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -873,11 +875,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private static void ComputeCycleRemainders(ulong cycleLength, ReadOnlySpan<ulong> primes, ReadOnlySpan<ulong> deltas, Span<ulong> remainders)
|
private static void ComputeCycleRemainders(ulong cycleLength, ReadOnlySpan<ulong> primes, ReadOnlySpan<ulong> deltas, Span<ulong> remainders)
|
||||||
{
|
{
|
||||||
if (cycleLength == 0UL || primes.Length == 0)
|
// Divisor cycles are validated before reaching the GPU helper, and prime batches are non-empty, so
|
||||||
{
|
// the defensive zero checks remain commented out here.
|
||||||
remainders.Clear();
|
// if (cycleLength == 0UL || primes.Length == 0)
|
||||||
return;
|
// {
|
||||||
}
|
// remainders.Clear();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong remainder = primes[0] % cycleLength;
|
ulong remainder = primes[0] % cycleLength;
|
||||||
remainders[0] = remainder;
|
remainders[0] = remainder;
|
||||||
@@ -893,6 +897,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
{
|
{
|
||||||
if (modulus == 0UL)
|
if (modulus == 0UL)
|
||||||
{
|
{
|
||||||
|
// A zero modulus indicates a missing cycle entry; preserve the fallback so snapshot regressions remain visible.
|
||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
private const int RemainderSlotCount = 4;
|
private const int RemainderSlotCount = 4;
|
||||||
|
|
||||||
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
||||||
private readonly object _sync = new();
|
|
||||||
private ulong _divisorLimit;
|
private ulong _divisorLimit;
|
||||||
private bool _isConfigured;
|
private bool _isConfigured;
|
||||||
|
|
||||||
@@ -64,34 +63,30 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
// Configuration happens once during startup, so no synchronization is required to cache the divisor limit.
|
||||||
{
|
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
_isConfigured = true;
|
||||||
_isConfigured = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
||||||
{
|
{
|
||||||
ulong allowedMax;
|
// The CLI always configures the tester during startup, so the historical guard is commented out to avoid branching.
|
||||||
int batchCapacity;
|
// if (!_isConfigured)
|
||||||
|
// {
|
||||||
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
|
// }
|
||||||
|
|
||||||
lock (_sync)
|
// Configuration is immutable, so the cached values can be read without locking.
|
||||||
{
|
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
if (!_isConfigured)
|
int batchCapacity = _gpuBatchSize;
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
// The GPU path always receives an allowed maximum well above the minimal divisor, so the legacy
|
||||||
batchCapacity = _gpuBatchSize;
|
// guard stays commented out to document the invariant without branching.
|
||||||
}
|
// if (allowedMax < 3UL)
|
||||||
|
// {
|
||||||
if (allowedMax < 3UL)
|
// divisorsExhausted = true;
|
||||||
{
|
// return true;
|
||||||
divisorsExhausted = true;
|
// }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool composite;
|
bool composite;
|
||||||
bool coveredRange;
|
bool coveredRange;
|
||||||
@@ -155,18 +150,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong divisorLimit;
|
// The GPU pipeline calls ConfigureFromMaxPrime before preparing candidates, so the legacy guard
|
||||||
|
// remains commented out to document the invariant without branching.
|
||||||
lock (_sync)
|
// if (!_isConfigured)
|
||||||
{
|
// {
|
||||||
if (!_isConfigured)
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
{
|
// }
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
divisorLimit = _divisorLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ulong divisorLimit = _divisorLimit;
|
||||||
for (int index = 0; index < primes.Length; index++)
|
for (int index = 0; index < primes.Length; index++)
|
||||||
{
|
{
|
||||||
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||||
@@ -222,7 +213,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
||||||
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
||||||
|
|
||||||
cycleCapacity = Math.Max(1, cycleCapacity);
|
// Batch resources expose a positive cycle capacity, so skip the redundant clamp here.
|
||||||
|
// cycleCapacity = Math.Max(1, cycleCapacity);
|
||||||
|
|
||||||
int chunkCapacity = Math.Min(batchCapacity, cycleCapacity);
|
int chunkCapacity = Math.Min(batchCapacity, cycleCapacity);
|
||||||
int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup;
|
int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup;
|
||||||
@@ -231,20 +223,22 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
chunkCapacity = Math.Min(chunkCapacity, maxThreadsPerGroup);
|
chunkCapacity = Math.Min(chunkCapacity, maxThreadsPerGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkCapacity <= 0)
|
// cycleCapacity already bounds chunkCapacity above zero, so the fallback would never execute.
|
||||||
{
|
// if (chunkCapacity <= 0)
|
||||||
chunkCapacity = cycleCapacity;
|
// {
|
||||||
}
|
// chunkCapacity = cycleCapacity;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
||||||
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
||||||
|
|
||||||
UInt128 twoP128 = (UInt128)prime << 1;
|
UInt128 twoP128 = (UInt128)prime << 1;
|
||||||
if (twoP128 == UInt128.Zero)
|
// Primes above one yield a non-zero 2p value, so the legacy zero guard remains commented out.
|
||||||
{
|
// if (twoP128 == UInt128.Zero)
|
||||||
coveredRange = true;
|
// {
|
||||||
return false;
|
// coveredRange = true;
|
||||||
}
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
UInt128 allowedMax128 = allowedMax;
|
UInt128 allowedMax128 = allowedMax;
|
||||||
UInt128 firstDivisor128 = twoP128 + UInt128.One;
|
UInt128 firstDivisor128 = twoP128 + UInt128.One;
|
||||||
@@ -255,11 +249,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
UInt128 maxK128 = (allowedMax128 - UInt128.One) / twoP128;
|
UInt128 maxK128 = (allowedMax128 - UInt128.One) / twoP128;
|
||||||
if (maxK128 == UInt128.Zero)
|
// Once the first divisor falls within the allowed range, at least one multiplier k is admissible,
|
||||||
{
|
// so keep the defensive zero check commented out.
|
||||||
coveredRange = true;
|
// if (maxK128 == UInt128.Zero)
|
||||||
return false;
|
// {
|
||||||
}
|
// coveredRange = true;
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
|
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
|
||||||
ulong currentK = 1UL;
|
ulong currentK = 1UL;
|
||||||
@@ -301,10 +297,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
chunkCount = (int)remainingK;
|
chunkCount = (int)remainingK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkCount <= 0)
|
// chunkCapacity and the remaining multiplier range are strictly positive here, so the zero
|
||||||
{
|
// chunk fallback never triggers on production scans.
|
||||||
break;
|
// if (chunkCount <= 0)
|
||||||
}
|
// {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
Span<ulong> candidateSpan = cycleCandidates.AsSpan(0, chunkCount);
|
Span<ulong> candidateSpan = cycleCandidates.AsSpan(0, chunkCount);
|
||||||
Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount);
|
Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount);
|
||||||
@@ -465,10 +463,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel,
|
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel,
|
||||||
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel)
|
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel)
|
||||||
{
|
{
|
||||||
if (count <= 0)
|
// GPU batches always contain at least one divisor gap, so the empty-count guard remains commented
|
||||||
{
|
// to eliminate redundant branching.
|
||||||
return;
|
// if (count <= 0)
|
||||||
}
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
deltaKernel(count, gapView, deltaView, modulus);
|
deltaKernel(count, gapView, deltaView, modulus);
|
||||||
SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic<int>(count);
|
SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic<int>(count);
|
||||||
@@ -491,11 +491,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
Span<byte> destination,
|
Span<byte> destination,
|
||||||
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> maskKernel)
|
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> maskKernel)
|
||||||
{
|
{
|
||||||
if (count <= 0)
|
// Candidate batches always provide at least one entry, so the zero-count guard stays commented out
|
||||||
{
|
// to avoid clearing spans unnecessarily.
|
||||||
destination.Clear();
|
// if (count <= 0)
|
||||||
return;
|
// {
|
||||||
}
|
// destination.Clear();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView);
|
maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView);
|
||||||
maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
||||||
@@ -503,10 +505,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private static byte FetchLastRemainder(ArrayView1D<byte, Stride1D.Dense> remainderView, int count)
|
private static byte FetchLastRemainder(ArrayView1D<byte, Stride1D.Dense> remainderView, int count)
|
||||||
{
|
{
|
||||||
if (count <= 0)
|
// Kernels always process at least one remainder in this path, so the empty-span guard remains
|
||||||
{
|
// commented to document the invariant.
|
||||||
return 0;
|
// if (count <= 0)
|
||||||
}
|
// {
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
byte value = 0;
|
byte value = 0;
|
||||||
remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1);
|
remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1);
|
||||||
@@ -519,10 +523,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
if (result >= modulus)
|
if (result >= modulus)
|
||||||
{
|
{
|
||||||
result -= modulus;
|
result -= modulus;
|
||||||
if (result >= modulus)
|
// The step deltas remain below the modulus, so a single subtraction suffices on this path.
|
||||||
{
|
// if (result >= modulus)
|
||||||
result %= modulus;
|
// {
|
||||||
}
|
// result %= modulus;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return (byte)result;
|
return (byte)result;
|
||||||
@@ -933,34 +938,32 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
// The CLI configures the GPU tester once at startup, so callers never request divisor limits before
|
||||||
{
|
// initialization completes.
|
||||||
if (!_isConfigured)
|
// if (!_isConfigured)
|
||||||
{
|
// {
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
}
|
// }
|
||||||
|
|
||||||
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DivisorScanSession CreateDivisorSession()
|
public DivisorScanSession CreateDivisorSession()
|
||||||
{
|
{
|
||||||
lock (_sync)
|
// Divisor sessions are handed out only after configuration, so this guard would never trigger on the
|
||||||
|
// production path.
|
||||||
|
// if (!_isConfigured)
|
||||||
|
// {
|
||||||
|
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
||||||
{
|
{
|
||||||
if (!_isConfigured)
|
session.Reset();
|
||||||
{
|
return session;
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
|
||||||
{
|
|
||||||
session.Reset();
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DivisorScanSession(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new DivisorScanSession(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession();
|
IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession();
|
||||||
|
|||||||
@@ -68,12 +68,17 @@ internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
int count = candidates.Length;
|
int count = candidates.Length;
|
||||||
if (count == 0)
|
// Candidate batches always carry at least one divisor on the by-divisor path, so the empty-count
|
||||||
{
|
// guard remains commented out to eliminate the branch.
|
||||||
return;
|
// if (count == 0)
|
||||||
}
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
EnsureCapacity(count);
|
if (count > _capacity)
|
||||||
|
{
|
||||||
|
EnsureCapacity(count);
|
||||||
|
}
|
||||||
|
|
||||||
Span<ulong> hostCandidateSpan = _hostCandidates.AsSpan(0, count);
|
Span<ulong> hostCandidateSpan = _hostCandidates.AsSpan(0, count);
|
||||||
Span<byte> hostMaskSpan = _hostMask.AsSpan(0, count);
|
Span<byte> hostMaskSpan = _hostMask.AsSpan(0, count);
|
||||||
@@ -115,7 +120,12 @@ internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int newCapacity = _capacity == 0 ? 1 : _capacity;
|
int newCapacity = _capacity;
|
||||||
|
// The evaluator always starts with a positive capacity, so the zero-capacity fallback remains commented.
|
||||||
|
// if (newCapacity == 0)
|
||||||
|
// {
|
||||||
|
// newCapacity = 1;
|
||||||
|
// }
|
||||||
while (newCapacity < required)
|
while (newCapacity < required)
|
||||||
{
|
{
|
||||||
newCapacity <<= 1;
|
newCapacity <<= 1;
|
||||||
@@ -188,10 +198,11 @@ internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable
|
|||||||
|
|
||||||
private static int ComputeRemainder(byte remainder, byte step, int index, int modulus)
|
private static int ComputeRemainder(byte remainder, byte step, int index, int modulus)
|
||||||
{
|
{
|
||||||
if (modulus == 0)
|
// Modulus lookups only cover {10, 8, 5, 3, 7, 11}, so the zero-modulus guard stays commented out.
|
||||||
{
|
// if (modulus == 0)
|
||||||
return 0;
|
// {
|
||||||
}
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
long value = remainder;
|
long value = remainder;
|
||||||
value += (long)step * index;
|
value += (long)step * index;
|
||||||
|
|||||||
@@ -54,10 +54,12 @@ internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
int length = moduli.Length;
|
int length = moduli.Length;
|
||||||
if (length == 0)
|
// Montgomery batches always include at least one modulus on the by-divisor path, so the empty-length
|
||||||
{
|
// guard stays commented out to avoid branching.
|
||||||
return;
|
// if (length == 0)
|
||||||
}
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
EnsureCapacity(length);
|
EnsureCapacity(length);
|
||||||
|
|
||||||
@@ -83,7 +85,12 @@ internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int newCapacity = _capacity == 0 ? 1 : _capacity;
|
int newCapacity = _capacity;
|
||||||
|
// The builder always starts with a positive capacity, so the zero-capacity fallback remains commented.
|
||||||
|
// if (newCapacity == 0)
|
||||||
|
// {
|
||||||
|
// newCapacity = 1;
|
||||||
|
// }
|
||||||
while (newCapacity < required)
|
while (newCapacity < required)
|
||||||
{
|
{
|
||||||
newCapacity <<= 1;
|
newCapacity <<= 1;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
|
|||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
return;
|
throw new InvalidOperationException("MersenneNumberDivisorRemainderGpuStepper was disposed more than once.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
@@ -71,15 +71,18 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
|
|||||||
throw new ObjectDisposedException(nameof(MersenneNumberDivisorRemainderGpuStepper));
|
throw new ObjectDisposedException(nameof(MersenneNumberDivisorRemainderGpuStepper));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (advanceCount <= 0 || remainders.Length == 0)
|
// GPU remainder batches always advance by a positive chunk count and operate on fixed-size tables, so
|
||||||
{
|
// the defensive early-return remains commented out.
|
||||||
return;
|
// if (advanceCount <= 0 || remainders.Length == 0)
|
||||||
}
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
if (remainders.Length != _length)
|
// Production code always calls the stepper with a fixed-length table, so the historical length guard stays commented.
|
||||||
{
|
// if (remainders.Length != _length)
|
||||||
throw new ArgumentException("Remainder buffer length must match the configured length.");
|
// {
|
||||||
}
|
// throw new ArgumentException("Remainder buffer length must match the configured length.");
|
||||||
|
// }
|
||||||
|
|
||||||
Span<byte> hostRemainderSpan = _hostRemainders.AsSpan(0, _length);
|
Span<byte> hostRemainderSpan = _hostRemainders.AsSpan(0, _length);
|
||||||
remainders.CopyTo(hostRemainderSpan);
|
remainders.CopyTo(hostRemainderSpan);
|
||||||
@@ -99,17 +102,20 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
|
|||||||
private static void AdvanceRemaindersKernel(Index1D index, int advanceCount, ArrayView<byte> remainders, ArrayView<byte> steps, ArrayView<byte> results)
|
private static void AdvanceRemaindersKernel(Index1D index, int advanceCount, ArrayView<byte> remainders, ArrayView<byte> steps, ArrayView<byte> results)
|
||||||
{
|
{
|
||||||
int idx = index;
|
int idx = index;
|
||||||
if (idx >= remainders.Length)
|
// Kernel launches match the remainder table length, so the bounds guard stays commented to avoid redundant branching.
|
||||||
{
|
// if (idx >= remainders.Length)
|
||||||
return;
|
// {
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
int modulus = GetModulus(idx);
|
int modulus = GetModulus(idx);
|
||||||
if (modulus == 0)
|
// The modulus selector only emits positive bases (10, 8, 5, 3, 7, 11) for the configured indices, so the zero-modulus
|
||||||
{
|
// fallback remains commented out.
|
||||||
results[idx] = remainders[idx];
|
// if (modulus == 0)
|
||||||
return;
|
// {
|
||||||
}
|
// results[idx] = remainders[idx];
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
int remainder = remainders[idx] % modulus;
|
int remainder = remainders[idx] % modulus;
|
||||||
int step = steps[idx] % modulus;
|
int step = steps[idx] % modulus;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable
|
|||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
return;
|
throw new InvalidOperationException("MersenneNumberDivisorResidueGpuEvaluator was disposed more than once.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
@@ -51,10 +51,12 @@ internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
int length = exponents.Length;
|
int length = exponents.Length;
|
||||||
if (length == 0)
|
// Residue batches always include at least one exponent when scheduled from the by-divisor scanner, so
|
||||||
{
|
// the empty-length guard remains commented out to avoid branching.
|
||||||
return;
|
// if (length == 0)
|
||||||
}
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
EnsureCapacity(length);
|
EnsureCapacity(length);
|
||||||
|
|
||||||
@@ -79,10 +81,11 @@ internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
int newCapacity = _capacity;
|
int newCapacity = _capacity;
|
||||||
if (newCapacity == 0)
|
// The evaluator always starts with a positive capacity, so the zero-capacity fallback stays commented.
|
||||||
{
|
// if (newCapacity == 0)
|
||||||
newCapacity = 1;
|
// {
|
||||||
}
|
// newCapacity = 1;
|
||||||
|
// }
|
||||||
|
|
||||||
while (newCapacity < required)
|
while (newCapacity < required)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -180,11 +180,13 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxPrime <= 1UL)
|
// EvenPerfectBitScanner removes candidates <= 1 before reaching this point,
|
||||||
{
|
// so this guard never triggers during production runs.
|
||||||
Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor.");
|
// if (maxPrime <= 1UL)
|
||||||
return;
|
// {
|
||||||
}
|
// Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor.");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
tester.ConfigureFromMaxPrime(maxPrime);
|
tester.ConfigureFromMaxPrime(maxPrime);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user