From 6e89700f80c75d98110ee6828f76e47ef1317114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Mon, 13 Oct 2025 20:41:00 +0200 Subject: [PATCH] Clarify invariants across GPU divisor helpers --- ...MersenneNumberDivisorByDivisorCpuTester.cs | 50 ++++++++----------- ...MersenneNumberDivisorByDivisorGpuTester.cs | 28 ++++------- ...senneNumberDivisorCandidateGpuEvaluator.cs | 5 +- ...ersenneNumberDivisorRemainderGpuStepper.cs | 11 ++-- ...ersenneNumberDivisorResidueGpuEvaluator.cs | 2 +- 5 files changed, 44 insertions(+), 52 deletions(-) diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index b74761f..1e9e937 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -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)); // } - ulong divisorLimit; - - lock (_sync) + if (!_isConfigured) { - if (!_isConfigured) - { - throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); - } - - divisorLimit = _divisorLimit; + throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); } + // 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++) { allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit); @@ -172,21 +168,18 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() { - lock (_sync) + if (!_isConfigured) { - if (!_isConfigured) - { - 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); + 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); } private void ReturnSession(DivisorScanSession session) @@ -323,11 +316,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv return true; } - // DivisorCycleCache guarantees a non-zero cycle for every divisor reached on the EvenPerfectBitScanner path. - // if (divisorCycle == 0UL) - // { - // Console.WriteLine($"Divisor cycle was not calculated for {prime}"); - // } + 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}"); + } } divisor += step; @@ -830,7 +823,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv int newCapacity = _bufferCapacity; 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; } @@ -908,6 +901,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { if (modulus == 0UL) { + // A zero modulus indicates a missing cycle entry; preserve the fallback so snapshot regressions remain visible. return 0UL; } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 2974517..5fc08b4 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -64,28 +64,22 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv public void ConfigureFromMaxPrime(ulong maxPrime) { - lock (_sync) - { - _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); - _isConfigured = true; - } + // Configuration happens once during startup, so no synchronization is required to cache the divisor limit. + _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); + _isConfigured = true; } public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null) { - ulong allowedMax; - int batchCapacity; + // The CLI always configures the tester during startup, so the historical guard is commented out to avoid branching. + // if (!_isConfigured) + // { + // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + // } - lock (_sync) - { - if (!_isConfigured) - { - throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); - } - - allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); - batchCapacity = _gpuBatchSize; - } + // Configuration is immutable, so the cached values can be read without locking. + ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); + int batchCapacity = _gpuBatchSize; // 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. diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorCandidateGpuEvaluator.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorCandidateGpuEvaluator.cs index b64aad2..4177fc7 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorCandidateGpuEvaluator.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorCandidateGpuEvaluator.cs @@ -75,7 +75,10 @@ internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable // return; // } - EnsureCapacity(count); + if (count > _capacity) + { + EnsureCapacity(count); + } Span hostCandidateSpan = _hostCandidates.AsSpan(0, count); Span hostMaskSpan = _hostMask.AsSpan(0, count); diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorRemainderGpuStepper.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorRemainderGpuStepper.cs index 9dd132f..721a519 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorRemainderGpuStepper.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorRemainderGpuStepper.cs @@ -52,7 +52,7 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable { if (_disposed) { - return; + throw new InvalidOperationException("MersenneNumberDivisorRemainderGpuStepper was disposed more than once."); } _disposed = true; @@ -78,10 +78,11 @@ internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable // return; // } - if (remainders.Length != _length) - { - throw new ArgumentException("Remainder buffer length must match the configured 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."); + // } Span hostRemainderSpan = _hostRemainders.AsSpan(0, _length); remainders.CopyTo(hostRemainderSpan); diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs index 8970fc2..72c60ad 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs @@ -33,7 +33,7 @@ internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable { if (_disposed) { - return; + throw new InvalidOperationException("MersenneNumberDivisorResidueGpuEvaluator was disposed more than once."); } _disposed = true;