Clarify invariants across GPU divisor helpers

This commit is contained in:
Marek Lemańczyk committed 2025-10-13 20:41:00 +02:00
1 parent bccb2f43c9
commit 6e89700f80
5 files changed
+44 -52

No files matched your search

@@ -152,18 +152,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : 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; if (!_isConfigured)
lock (_sync)
{ {
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;
} }
// Configuration is immutable after startup, so reading the cached limit does not require locking.
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);
@@ -172,21 +168,18 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
{ {
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.");
}
if (_sessionPool.TryTake(out DivisorScanSession? session))
{
session.Reset();
return session;
}
return new DivisorScanSession(this);
} }
if (_sessionPool.TryTake(out DivisorScanSession? session))
{
session.Reset();
return session;
}
return new DivisorScanSession(this);
} }
private void ReturnSession(DivisorScanSession session) private void ReturnSession(DivisorScanSession session)
@@ -323,11 +316,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return true; return true;
} }
// DivisorCycleCache guarantees a non-zero cycle for every divisor reached on the EvenPerfectBitScanner path. 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}");
// } }
} }
divisor += step; divisor += step;
@@ -830,7 +823,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;
} }
@@ -908,6 +901,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;
} }
@@ -64,28 +64,22 @@ 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);
batchCapacity = _gpuBatchSize;
}
// The GPU path always receives an allowed maximum well above the minimal divisor, so the legacy // The GPU path always receives an allowed maximum well above the minimal divisor, so the legacy
// guard stays commented out to document the invariant without branching. // guard stays commented out to document the invariant without branching.
@@ -75,7 +75,10 @@ internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable
// return; // 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);
@@ -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;
@@ -78,10 +78,11 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
// return; // 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);
@@ -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;