Clarify invariants across GPU divisor helpers
This commit is contained in:
1 parent
bccb2f43c9
commit
6e89700f80
5 files changed
+28
-36
No files matched your search
@@ -152,17 +152,13 @@ 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;
|
|
||||||
|
|
||||||
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;
|
// 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++)
|
||||||
{
|
{
|
||||||
@@ -171,8 +167,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
{
|
||||||
if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
{
|
{
|
||||||
@@ -187,7 +181,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
return new DivisorScanSession(this);
|
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;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if (count > _capacity)
|
||||||
|
{
|
||||||
EnsureCapacity(count);
|
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;
|
||||||
|
|||||||
Reference in new issue
Block a user