--mersenne=bydivisor testers and sessions integrated and converted to structs.

This commit is contained in:
mlemanczyk committed 2025-12-18 05:07:31 +01:00
1 parent 3c80221105
commit 2abc6aae49
32 files changed
+3814 -1505

No files matched your search

+1 -1
View File
@@ -48,7 +48,7 @@ namespace PerfectNumbers.Core.Gpu
}
}
public static readonly AcceleratorPool Shared = new(PerfectNumberConstants.RollingAccelerators);
public static readonly AcceleratorPool Shared = new(EnvironmentConfiguration.RollingAccelerators);
private readonly int Capacity;
@@ -6,8 +6,8 @@ namespace PerfectNumbers.Core.Gpu.Accelerators;
public static class AcceleratorStreamPool
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly PollingSemaphore[] _locks = new PollingSemaphore[PerfectNumberConstants.RollingAccelerators];
private static readonly ConcurrentFixedCapacityStack<AcceleratorStream>[] _streams = new ConcurrentFixedCapacityStack<AcceleratorStream>[PerfectNumberConstants.RollingAccelerators];
private static readonly PollingSemaphore[] _locks = new PollingSemaphore[EnvironmentConfiguration.RollingAccelerators];
private static readonly ConcurrentFixedCapacityStack<AcceleratorStream>[] _streams = new ConcurrentFixedCapacityStack<AcceleratorStream>[EnvironmentConfiguration.RollingAccelerators];
private static PollingSemaphore CreateLock() => new(PerfectNumberConstants.ThreadsByAccelerator, TimeSpan.FromMilliseconds(1000), 1);
@@ -8,7 +8,7 @@ internal readonly struct HeuristicCombinedGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly HeuristicCombinedGpuTables[] _sharedTables = new HeuristicCombinedGpuTables[PerfectNumberConstants.RollingAccelerators];
private static readonly HeuristicCombinedGpuTables[] _sharedTables = new HeuristicCombinedGpuTables[EnvironmentConfiguration.RollingAccelerators];
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new(_accelerators[acceleratorIndex], stream);
@@ -7,9 +7,9 @@ internal sealed class HeuristicGroupABGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly HeuristicGroupABGpuTables[] _sharedTables = new HeuristicGroupABGpuTables[PerfectNumberConstants.RollingAccelerators];
private static readonly HeuristicGroupABGpuTables[] _sharedTables = new HeuristicGroupABGpuTables[EnvironmentConfiguration.RollingAccelerators];
private static readonly SemaphoreSlim[] _locks = [..Enumerable.Range(1, PerfectNumberConstants.RollingAccelerators).Select(_ => new SemaphoreSlim(1))];
private static readonly SemaphoreSlim[] _locks = [..Enumerable.Range(1, EnvironmentConfiguration.RollingAccelerators).Select(_ => new SemaphoreSlim(1))];
internal static HeuristicGroupABGpuTables EnsureStaticTables(int acceleratorIndex, AcceleratorStream stream)
{
@@ -7,7 +7,7 @@ namespace PerfectNumbers.Core.Gpu.Accelerators;
internal readonly struct LastDigitGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly LastDigitGpuTables[] _sharedTables = new LastDigitGpuTables[PerfectNumberConstants.RollingAccelerators];
private static readonly LastDigitGpuTables[] _sharedTables = new LastDigitGpuTables[EnvironmentConfiguration.RollingAccelerators];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static LastDigitGpuTables GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
@@ -9,6 +9,8 @@ public static class GpuPrimeWorkLimiter
public static void Acquire() => _semaphore.Wait();
public static void Release() => _semaphore.Release();
public static int CurrentLimit => _currentLimit;
public static void SetLimit(int value)
{
if (value <= 0)
@@ -0,0 +1,95 @@
using System.Globalization;
using System.Numerics;
namespace PerfectNumbers.Core.Gpu;
internal struct MersenneNumberDivisorByDivisorGpuPrimeScanSession
{
private readonly MersenneNumberDivisorByDivisorGpuTester _tester;
private readonly ulong _divisorLimit;
private readonly Action _markComposite;
private readonly Action _clearComposite;
private readonly Action<ulong, bool, bool, bool, BigInteger> _printResult;
public MersenneNumberDivisorByDivisorGpuPrimeScanSession(
MersenneNumberDivisorByDivisorGpuTester prototype,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool, BigInteger> printResult)
{
_tester = new MersenneNumberDivisorByDivisorGpuTester
{
MinK = EnvironmentConfiguration.MinK,
};
_divisorLimit = prototype.DivisorLimit;
_tester.SetDivisorLimitForClone(_divisorLimit);
_markComposite = markComposite;
_clearComposite = clearComposite;
_printResult = printResult;
}
public void ProcessPrime(ulong prime)
{
try
{
Console.WriteLine($"Processing {prime}");
string stateFile = Path.Combine(
PerfectNumberConstants.ByDivisorStateDirectory,
prime.ToString(CultureInfo.InvariantCulture) + ".bin");
ConfigureForPrime(stateFile);
bool isPrime = _tester.IsPrime(prime, out bool divisorsExhausted, out BigInteger divisor);
if (!isPrime)
{
if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile))
{
File.Delete(stateFile);
}
_markComposite();
_printResult(prime, true, true, false, divisor);
Console.WriteLine($"Finished processing {prime}");
return;
}
_clearComposite();
_printResult(prime, true, divisorsExhausted, true, divisor);
Console.WriteLine($"Finished processing {prime}");
}
catch (Exception ex)
{
Console.WriteLine($"Error processing {ex.Message} {ex.StackTrace}");
Environment.Exit(1);
}
}
private void ConfigureForPrime(string stateFile)
{
BigInteger resumeK = EnvironmentConfiguration.MinK;
if (File.Exists(stateFile))
{
if (MersenneNumberDivisorByDivisorTester.TryReadLastSavedK(stateFile, out BigInteger lastK))
{
resumeK = lastK + BigInteger.One;
_tester.ResumeFromState(stateFile, lastK, resumeK);
}
else
{
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
}
}
else
{
_tester.ResumeFromState(stateFile, BigInteger.Zero, resumeK);
}
_tester.ResetStateTracking();
}
public void Dispose()
{
}
}
@@ -14,67 +14,46 @@ namespace PerfectNumbers.Core.Gpu;
/// </summary>
public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDivisorByDivisorTester
{
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
// EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards,
// so the synchronization fields from the previous implementation remain commented out here.
private readonly PrimeOrderCalculatorAccelerator Accelerator = PrimeOrderCalculatorAccelerator.Rent(1);
private ulong _divisorLimit;
private BigInteger _minK = BigInteger.One;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal void SetDivisorLimitForClone(ulong divisorLimit) => _divisorLimit = divisorLimit;
public BigInteger MinK = BigInteger.One;
public string? StateFilePath;
private readonly ConcurrentFixedCapacityStack<MersenneNumberDivisorByDivisorAccelerator>[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentFixedCapacityStack<MersenneNumberDivisorByDivisorAccelerator>(PerfectNumberConstants.DefaultPoolCapacity))];
private readonly ConcurrentFixedCapacityStack<DivisorScanSession> _sessionPool = new(PerfectNumberConstants.DefaultPoolCapacity);
public int GpuBatchSize
{
get => _gpuBatchSize;
set => _gpuBatchSize = value < 1 ? 1 : value;
}
int IMersenneNumberDivisorByDivisorTester.BatchSize
{
get => GpuBatchSize;
set => GpuBatchSize = value;
}
public BigInteger MinK
{
get => _minK;
set => _minK = value < BigInteger.One ? BigInteger.One : value;
}
public string? StateFilePath { get; set; }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void ResetStateTracking()
{
}
public void ResumeFromState(BigInteger lastSavedK)
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void ResumeFromState(in string stateFile, in BigInteger lastSavedK, in BigInteger minK)
{
StateFilePath = stateFile;
if (lastSavedK > ulong.MaxValue)
{
throw new NotSupportedException("GPU by-divisor tester does not support MinK above UInt64.");
ThrowNotSupportedLastSavedK();
return; // We'll never get to here
}
_minK = lastSavedK + BigInteger.One;
MinK = minK;
}
public void ConfigureFromMaxPrime(ulong maxPrime)
{
// EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards,
// so synchronization and runtime configuration guards are unnecessary here.
private static void ThrowNotSupportedLastSavedK() => throw new NotSupportedException("GPU by-divisor tester does not support MinK above UInt64.");
_divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime);
}
public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out BigInteger divisor)
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
{
ulong allowedMax;
int batchCapacity;
// EvenPerfectBitScanner only calls into this tester after configuring it once, so we can read the cached values without locking.
allowedMax = ComputeAllowedMaxDivisorGpu(prime, _divisorLimit);
batchCapacity = _gpuBatchSize;
// Production scans never shrink the divisor window below three, so this guard stays commented out.
// if (allowedMax < 3UL)
@@ -84,17 +63,16 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
// }
bool composite;
bool coveredRange;
ulong lastProcessed;
// GpuPrimeWorkLimiter();
// int acceleratorIndex = AcceleratorPool.Shared.Rent();
// var accelerator = _accelerators[acceleratorIndex];
PrimeOrderCalculatorAccelerator gpu = Accelerator;
var acceleratorIndex = gpu.AcceleratorIndex;
// Monitor.Enter(gpuLease.ExecutionLock);
var resources = RentBatchResources(acceleratorIndex, batchCapacity);
var resources = RentBatchResources(acceleratorIndex, EnvironmentConfiguration.GpuBatchSize);
composite = CheckDivisors(
gpu,
@@ -115,8 +93,8 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
resources.Offsets,
resources.Counts,
resources.Cycles,
out lastProcessed,
out coveredRange);
out ulong lastProcessed,
out bool coveredRange);
ReturnBatchResources(acceleratorIndex, resources);
// Monitor.Exit(gpuLease.ExecutionLock);
@@ -134,17 +112,19 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private ulong GetMinKOrThrow()
{
if (_minK > ulong.MaxValue)
if (MinK > ulong.MaxValue)
{
throw new NotSupportedException("GPU by-divisor tester does not support MinK above UInt64.");
ThrowNotSupportedLastSavedK();
return 0UL;
}
return (ulong)_minK;
return (ulong)MinK;
}
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
public void PrepareCandidates(ulong maxPrime, in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{
// The scanner always supplies matching spans, so the previous validation remains commented out.
// if (allowedMaxValues.Length < primes.Length)
@@ -165,7 +145,12 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
// divisorLimit = _divisorLimit;
// }
divisorLimit = _divisorLimit;
// EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards,
// so synchronization and runtime configuration guards are unnecessary here.
divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime);
_divisorLimit = divisorLimit;
for (int index = 0; index < primes.Length; index++)
{
@@ -526,12 +511,12 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
private readonly Accelerator _accelerator;
private readonly PrimeOrderCalculatorAccelerator _primeOrderCalculatorAccelerator;
private readonly AcceleratorStream _stream;
private Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!;
private MemoryBuffer1D<ulong, Stride1D.Dense> _cycleBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _firstHitBuffer = null!;
private readonly Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
private readonly MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!;
private readonly MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!;
private readonly MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!;
private readonly MemoryBuffer1D<ulong, Stride1D.Dense> _cycleBuffer = null!;
private readonly MemoryBuffer1D<int, Stride1D.Dense> _firstHitBuffer = null!;
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentsBuffer = null!;
private MemoryBuffer1D<byte, Stride1D.Dense> _hitBuffer = null!;
private ulong[] _hostBuffer = null!;
@@ -546,7 +531,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
Accelerator accelerator = gpu.Accelerator;
_accelerator = accelerator;
_stream = accelerator.CreateStream();
_capacity = Math.Max(1, owner._gpuBatchSize);
// lock (accelerator)
{
@@ -559,11 +543,9 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
int desiredCapacity = PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount;
_capacity = desiredCapacity;
// lock (accelerator)
{
_exponentsBuffer = accelerator.Allocate1D<ulong>(desiredCapacity);
_hitBuffer = accelerator.Allocate1D<byte>(desiredCapacity);
}
_exponentsBuffer = accelerator.Allocate1D<ulong>(desiredCapacity);
_hitBuffer = accelerator.Allocate1D<byte>(desiredCapacity);
_hostBuffer = ThreadStaticPools.UlongPool.Rent(desiredCapacity);
@@ -729,23 +711,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public ulong GetAllowedMaxDivisor(ulong prime)
{
// The configuration is immutable after setup, so the cached limit can be read without locking.
// lock (_sync)
// {
// if (!_isConfigured)
// {
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
// }
// return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit);
// }
return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu)
{
@@ -7,7 +7,7 @@ public readonly struct SmallPrimeFactorGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly SmallPrimeFactorGpuTables?[] _sharedTables = new SmallPrimeFactorGpuTables?[PerfectNumberConstants.RollingAccelerators];
private static readonly SmallPrimeFactorGpuTables?[] _sharedTables = new SmallPrimeFactorGpuTables?[EnvironmentConfiguration.RollingAccelerators];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static SmallPrimeFactorGpuTables? GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];