Remove configuration locks from by-divisor testers

This commit is contained in:
Marek Lemańczyk committed 2025-10-13 20:41:07 +02:00
1 parent 6e89700f80
commit b0ba6401ca
2 files changed
+61 -88

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,80 +32,55 @@ 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) 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) 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; 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.");
}
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
} }
ulong divisorLimit = _divisorLimit;
ulong allowedMax = ComputeAllowedMaxDivisor(prime, divisorLimit);
if (allowedMax < 3UL) if (allowedMax < 3UL)
{ {
divisorsExhausted = true; divisorsExhausted = true;
@@ -128,10 +101,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (processedCount > 0UL) if (processedCount > 0UL)
{ {
lock (_sync) UpdateStatusUnsafe(processedCount);
{
UpdateStatusUnsafe(processedCount);
}
} }
if (composite) if (composite)
@@ -603,18 +573,34 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return; return;
} }
ulong interval = PerfectNumberConstants.ConsoleInterval; int interval = PerfectNumberConstants.ConsoleInterval;
// PerfectNumberConstants.ConsoleInterval is a positive constant, so this guard never triggers on EvenPerfectBitScanner. // PerfectNumberConstants.ConsoleInterval is a positive constant, so this guard never triggers on EvenPerfectBitScanner.
// if (interval == 0UL) // if (interval <= 0)
// { // {
// _lastStatusDivisor = 0UL; // Volatile.Write(ref _lastStatusDivisor, 0);
// return; // return;
// } // }
ulong total = _lastStatusDivisor + processedCount; int processedDelta = (int)(processedCount % (uint)interval);
// TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI if (processedDelta == 0)
// status benchmarks so we avoid `%` in this hot loop while still wrapping progress correctly. {
_lastStatusDivisor = total % interval; return;
}
while (true)
{
int previous = Volatile.Read(ref _lastStatusDivisor);
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)
@@ -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;
@@ -151,18 +150,12 @@ 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; 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;
} }
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);
@@ -943,34 +936,28 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
public ulong GetAllowedMaxDivisor(ulong prime) public ulong GetAllowedMaxDivisor(ulong prime)
{ {
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.");
}
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
} }
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
} }
public DivisorScanSession CreateDivisorSession() public DivisorScanSession 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);
} }
IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession();