diff --git a/PerfectNumbers.Core/DivisorCycleCache.cs b/PerfectNumbers.Core/DivisorCycleCache.cs index f9fab73..26e7a6b 100644 --- a/PerfectNumbers.Core/DivisorCycleCache.cs +++ b/PerfectNumbers.Core/DivisorCycleCache.cs @@ -17,6 +17,7 @@ public sealed class DivisorCycleCache private const byte ByteOne = 1; private const int StackBufferThreshold = 128; private readonly Accelerator _accelerator; + private readonly int _acceleratorIndex; private readonly Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> _gpuKernel; private volatile ulong[] _snapshot; private volatile bool _useGpuGeneration = true; @@ -38,10 +39,14 @@ public sealed class DivisorCycleCache public int PreferredBatchSize => _divisorCyclesBatchSize; + // private static readonly Accelerator[] _accelerators; + private DivisorCycleCache(int divisorCyclesBatchSize) { _divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize); - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + _acceleratorIndex = acceleratorIndex; + Accelerator accelerator = AcceleratorPool.Shared.Accelerators[acceleratorIndex]; _accelerator = accelerator; _gpuKernel = LoadKernel(accelerator); _snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); @@ -311,7 +316,8 @@ public sealed class DivisorCycleCache // GpuPrimeWorkLimiter.Acquire(); int pending; var kernel = _gpuKernel; - var stream = AcceleratorStreamPool.Rent(_accelerator); + var acceleratorIndex = _acceleratorIndex; + var stream = AcceleratorStreamPool.Rent(acceleratorIndex); divisorBuffer!.View.CopyFromCPU(stream, divisors); powBuffer!.View.CopyFromCPU(stream, powSpan); @@ -339,7 +345,7 @@ public sealed class DivisorCycleCache resultBuffer.View.CopyToCPU(stream, resultSpan); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); resultSpan.CopyTo(destination); if (rentedPow is not null) diff --git a/PerfectNumbers.Core/Gpu/AcceleratorPool.cs b/PerfectNumbers.Core/Gpu/AcceleratorPool.cs index 2f5f8ac..0024155 100644 --- a/PerfectNumbers.Core/Gpu/AcceleratorPool.cs +++ b/PerfectNumbers.Core/Gpu/AcceleratorPool.cs @@ -11,7 +11,7 @@ namespace PerfectNumbers.Core.Gpu private int _index; - public Accelerator Rent() + public int Rent() { Atomic.CompareExchange(ref _index, Capacity, 0); var index = Atomic.Add(ref _index, 1); @@ -20,27 +20,31 @@ namespace PerfectNumbers.Core.Gpu index -= Capacity; } - return Accelerators[index]; + return index; } - public void Return(Accelerator accelerator) + public void Return(int acceleratorIndex) { // Intentionally left empty - there's nothing to do here. We're not really renting anything. } public static readonly AcceleratorPool Shared = new(PerfectNumberConstants.RollingAccelerators); + private readonly int Capacity; public AcceleratorPool(int capacity) { + Console.WriteLine("Creating accelerators"); Capacity = capacity; var context = Context.CreateDefault(); var device = context.GetPreferredDevice(false); Context = context; Device = device; int acceleratorsInContext = 0; + int acceleratorIndex = 0; Accelerators = [.. Enumerable.Range(0, capacity).Select(_ => { + Console.WriteLine($"Preparing accelerator {acceleratorIndex++}"); Accelerator accelerator = device.CreateAccelerator(context); acceleratorsInContext++; if (acceleratorsInContext >= 128) diff --git a/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs b/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs index e06b040..c6a5317 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs @@ -5,38 +5,38 @@ namespace PerfectNumbers.Core.Gpu.Accelerators; public static class AcceleratorStreamPool { - private static readonly Dictionary> _streams = new(PerfectNumberConstants .RollingAccelerators << 1); + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + private static readonly SemaphoreSlim[] _locks = new SemaphoreSlim[PerfectNumberConstants.RollingAccelerators]; + private static readonly ConcurrentQueue[] _streams = new ConcurrentQueue [PerfectNumberConstants.RollingAccelerators]; - private static SemaphoreSlim CreateLock() => new(PerfectNumberConstants.ThreadsByStream); + private static SemaphoreSlim CreateLock() => new(PerfectNumberConstants.ThreadsByAccelerator); - private static readonly Dictionary _locks = new(PerfectNumberConstants.RollingAccelerators << 1); // private static int _rented; - public static AcceleratorStream Rent(Accelerator accelerator) + public static AcceleratorStream Rent(int acceleratorIndex) { - var streamLock = _locks[accelerator]; - var queue = _streams[accelerator]; + var streamLock = _locks[acceleratorIndex]; + var queue = _streams[acceleratorIndex]; streamLock.Wait(); return queue.TryDequeue(out var stream) ? stream - : accelerator.CreateStream(); + : _accelerators[acceleratorIndex].CreateStream(); } - public static void Return(AcceleratorStream stream) + public static void Return(int acceleratorIndex, AcceleratorStream stream) { - Accelerator accelerator = stream.Accelerator; - var streamLock = _locks[accelerator]; - var queue = _streams[accelerator]; + var streamLock = _locks[acceleratorIndex]; + var queue = _streams[acceleratorIndex]; queue.Enqueue(stream); streamLock.Release(); } - public static void WarmUp(Accelerator accelerator) + public static void WarmUp(int acceleratorIndex) { - _streams.Add(accelerator, new()); - _ = _locks.TryAdd(accelerator, CreateLock()); + _streams[acceleratorIndex] = new(); + _locks[acceleratorIndex] = CreateLock(); } } diff --git a/PerfectNumbers.Core/Gpu/Accelerators/HeuristicPrimeTesterAccelerator.cs b/PerfectNumbers.Core/Gpu/Accelerators/HeuristicPrimeTesterAccelerator.cs index 4d57e00..30dd44e 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/HeuristicPrimeTesterAccelerator.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/HeuristicPrimeTesterAccelerator.cs @@ -131,9 +131,12 @@ internal sealed class HeuristicPrimeTesterAccelerator public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables; + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + internal HeuristicPrimeTesterAccelerator(int minBufferCapacity) { - Accelerator accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + Accelerator accelerator = _accelerators[acceleratorIndex]; Context = accelerator.Context; Accelerator = accelerator; @@ -141,7 +144,7 @@ internal sealed class HeuristicPrimeTesterAccelerator HeuristicTrialDivisionKernel = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel)); - AcceleratorStream stream = AcceleratorStreamPool.Rent(accelerator); + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); // GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream); @@ -149,7 +152,7 @@ internal sealed class HeuristicPrimeTesterAccelerator _heuristicDivisorTables = _sharedTables.CreateHeuristicDivisorTables(); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); } public void EnsureCapacity(int minCapacity) diff --git a/PerfectNumbers.Core/Gpu/Accelerators/LastDigitGpuTables.cs b/PerfectNumbers.Core/Gpu/Accelerators/LastDigitGpuTables.cs index 2e04865..4b08a2c 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/LastDigitGpuTables.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/LastDigitGpuTables.cs @@ -6,13 +6,14 @@ namespace PerfectNumbers.Core.Gpu.Accelerators; internal sealed class LastDigitGpuTables { - private static readonly Dictionary _sharedTables = new(PerfectNumberConstants.RollingAccelerators); + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + private static readonly LastDigitGpuTables[] _sharedTables = new LastDigitGpuTables[PerfectNumberConstants.RollingAccelerators]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static LastDigitGpuTables EnsureStaticTables(Accelerator accelerator) => _sharedTables[accelerator]; + internal static LastDigitGpuTables EnsureStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex]; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void WarmUp(Accelerator accelerator, AcceleratorStream stream) => _sharedTables[accelerator] = new LastDigitGpuTables(accelerator, stream); + internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new LastDigitGpuTables(_accelerators[acceleratorIndex], stream); internal LastDigitGpuTables(Accelerator accelerator, AcceleratorStream stream) { diff --git a/PerfectNumbers.Core/Gpu/Accelerators/PrimeOrderCalculatorAccelerator.cs b/PerfectNumbers.Core/Gpu/Accelerators/PrimeOrderCalculatorAccelerator.cs index 3ee253c..35c0369 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/PrimeOrderCalculatorAccelerator.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/PrimeOrderCalculatorAccelerator.cs @@ -1,7 +1,6 @@ using System.Runtime.CompilerServices; using ILGPU; using ILGPU.Runtime; -using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu.Kernels; namespace PerfectNumbers.Core.Gpu.Accelerators; @@ -19,8 +18,8 @@ public sealed class PrimeOrderCalculatorAccelerator var pool = _pool ??= []; if (!pool.TryDequeue(out var gpu)) { - Accelerator accelerator = AcceleratorPool.Shared.Rent(); - gpu = new(accelerator, PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount, PerfectNumberConstants.DefaultSpecialMaxFactorCapacity); + int acceleratorIndex = AcceleratorPool.Shared.Rent(); + gpu = new(acceleratorIndex, PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount, PerfectNumberConstants.DefaultSpecialMaxFactorCapacity); } else { @@ -65,22 +64,26 @@ public sealed class PrimeOrderCalculatorAccelerator { Console.WriteLine($"Preparing accelerator {i}..."); var accelerator = accelerators[i]; - AcceleratorStreamPool.WarmUp(accelerator); + AcceleratorStreamPool.WarmUp(i); // Don't take this from the pool as quick uploads of data to the accelerator consumes much of GPU's memory and throws. AcceleratorStream stream = accelerator.CreateStream(); - LastDigitGpuTables.WarmUp(accelerator, stream); + LastDigitGpuTables.WarmUp(i, stream); // SharedHeuristicGpuTables.EnsureStaticTables(accelerator, stream); // _ = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.None); // KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream); // GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream); stream.Synchronize(); stream.Dispose(); + accelerator.Synchronize(); } } #endregion + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + public readonly Accelerator Accelerator; + public readonly int AcceleratorIndex; public readonly MemoryBuffer1D Input; public readonly MemoryBuffer1D OutputUlong; public MemoryBuffer1D PrimeTestInput; @@ -161,9 +164,11 @@ public sealed class PrimeOrderCalculatorAccelerator } } - public PrimeOrderCalculatorAccelerator(Accelerator accelerator, int factorsCount, int primeTesterCapacity, int smallPrimeFactorSlotCount, int specialMaxFactorCapacity) + public PrimeOrderCalculatorAccelerator(int acceleratorIndex, int factorsCount, int primeTesterCapacity, int smallPrimeFactorSlotCount, int specialMaxFactorCapacity) { + var accelerator = _accelerators[acceleratorIndex]; Accelerator = accelerator; + AcceleratorIndex = acceleratorIndex; Input = accelerator.Allocate1D(1); OutputUlong = accelerator.Allocate1D(1); Pow2ModEntriesToTestOnDevice = accelerator.Allocate1D>(factorsCount); @@ -179,7 +184,7 @@ public sealed class PrimeOrderCalculatorAccelerator SpecialMaxCandidates = accelerator.Allocate1D(specialMaxFactorCapacity); SpecialMaxResult = accelerator.Allocate1D(1); - var sharedTables = LastDigitGpuTables.EnsureStaticTables(accelerator); + var sharedTables = LastDigitGpuTables.EnsureStaticTables(acceleratorIndex); DevicePrimesLastOne = sharedTables.DevicePrimesLastOne; DevicePrimesLastSeven = sharedTables.DevicePrimesLastSeven; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 44ba292..b0eb10b 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -17,18 +17,18 @@ namespace PerfectNumbers.Core.Gpu; /// public sealed 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 object _sync = new(); - private ulong _divisorLimit; + 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 object _sync = new(); + private ulong _divisorLimit; // private bool _isConfigured; [ThreadStatic] private static Dictionary, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>>? _kernel; - - private readonly ConcurrentBag _resourcePool = []; - private readonly ConcurrentBag _sessionPool = []; + + private readonly ConcurrentBag _resourcePool = []; + private readonly ConcurrentBag _sessionPool = []; private static Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> GetKernel(Accelerator accelerator) { @@ -46,490 +46,497 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv } public int GpuBatchSize - { - get => _gpuBatchSize; - set => _gpuBatchSize = value < 1 ? 1 : value; - } + { + get => _gpuBatchSize; + set => _gpuBatchSize = value < 1 ? 1 : value; + } - int IMersenneNumberDivisorByDivisorTester.BatchSize - { - get => GpuBatchSize; - set => GpuBatchSize = value; - } + int IMersenneNumberDivisorByDivisorTester.BatchSize + { + get => GpuBatchSize; + set => GpuBatchSize = value; + } - 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. - // lock (_sync) - // { - // _divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime); - // _isConfigured = true; - // } + 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. + // lock (_sync) + // { + // _divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime); + // _isConfigured = true; + // } - _divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime); - // _isConfigured = true; - } + _divisorLimit = ComputeDivisorLimitFromMaxPrimeGpu(maxPrime); + // _isConfigured = true; + } - public bool IsPrime(ulong prime, out bool divisorsExhausted) - { - ulong allowedMax; - int batchCapacity; + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; - // EvenPerfectBitScanner only calls into this tester after configuring it once, so we can read the cached values without locking. - // lock (_sync) - // { - // if (!_isConfigured) - // { - // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); - // } + public bool IsPrime(ulong prime, out bool divisorsExhausted) + { + ulong allowedMax; + int batchCapacity; - // allowedMax = ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); - // batchCapacity = _gpuBatchSize; - // } + // EvenPerfectBitScanner only calls into this tester after configuring it once, so we can read the cached values without locking. + // lock (_sync) + // { + // if (!_isConfigured) + // { + // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + // } - allowedMax = ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); - batchCapacity = _gpuBatchSize; + // allowedMax = ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); + // batchCapacity = _gpuBatchSize; + // } - // Production scans never shrink the divisor window below three, so this guard stays commented out. - // if (allowedMax < 3UL) - // { - // divisorsExhausted = true; - // return true; - // } + allowedMax = ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); + batchCapacity = _gpuBatchSize; - bool composite; - bool coveredRange; - ulong processedCount; - ulong lastProcessed; + // Production scans never shrink the divisor window below three, so this guard stays commented out. + // if (allowedMax < 3UL) + // { + // divisorsExhausted = true; + // return true; + // } - // GpuPrimeWorkLimiter(); - var accelerator = AcceleratorPool.Shared.Rent(); - var stream = accelerator.CreateStream(); + bool composite; + bool coveredRange; + ulong processedCount; + ulong lastProcessed; - // Monitor.Enter(gpuLease.ExecutionLock); + // GpuPrimeWorkLimiter(); + int acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; + var stream = accelerator.CreateStream(); - var kernel = GetKernel(accelerator); - var resources = RentBatchResources(accelerator, batchCapacity); + // Monitor.Enter(gpuLease.ExecutionLock); - composite = CheckDivisors( - prime, - allowedMax, - kernel, - stream, - resources.DivisorDataBuffer, - resources.OffsetBuffer, - resources.CountBuffer, - resources.CycleBuffer, - resources.ExponentBuffer, - resources.HitsBuffer, - resources.HitIndexBuffer, - resources.Divisors, - resources.Exponents, - resources.DivisorData, - resources.Offsets, - resources.Counts, - resources.Cycles, - out lastProcessed, - out coveredRange, - out processedCount); + var kernel = GetKernel(accelerator); + var resources = RentBatchResources(accelerator, batchCapacity); + + composite = CheckDivisors( + prime, + allowedMax, + kernel, + stream, + resources.DivisorDataBuffer, + resources.OffsetBuffer, + resources.CountBuffer, + resources.CycleBuffer, + resources.ExponentBuffer, + resources.HitsBuffer, + resources.HitIndexBuffer, + resources.Divisors, + resources.Exponents, + resources.DivisorData, + resources.Offsets, + resources.Counts, + resources.Cycles, + out lastProcessed, + out coveredRange, + out processedCount); stream.Dispose(); - ReturnBatchResources(resources); + ReturnBatchResources(resources); // Monitor.Exit(gpuLease.ExecutionLock); // GpuPrimeWorkLimiter.Release(); - - if (composite) - { - divisorsExhausted = true; - return false; - } - divisorsExhausted = coveredRange; - return true; - } + if (composite) + { + divisorsExhausted = true; + return false; + } - public void PrepareCandidates(in ReadOnlySpan primes, Span allowedMaxValues) - { - // The scanner always supplies matching spans, so the previous validation remains commented out. - // if (allowedMaxValues.Length < primes.Length) - // { - // throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues)); - // } + divisorsExhausted = coveredRange; + return true; + } - ulong divisorLimit; + public void PrepareCandidates(in ReadOnlySpan primes, Span allowedMaxValues) + { + // The scanner always supplies matching spans, so the previous validation remains commented out. + // if (allowedMaxValues.Length < primes.Length) + // { + // throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues)); + // } - // EvenPerfectBitScanner configures the tester exactly once before preparing candidates. - // lock (_sync) - // { - // if (!_isConfigured) - // { - // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); - // } + ulong divisorLimit; - // divisorLimit = _divisorLimit; - // } + // EvenPerfectBitScanner configures the tester exactly once before preparing candidates. + // lock (_sync) + // { + // if (!_isConfigured) + // { + // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + // } - divisorLimit = _divisorLimit; + // divisorLimit = _divisorLimit; + // } - for (int index = 0; index < primes.Length; index++) - { - allowedMaxValues[index] = ComputeAllowedMaxDivisorGpu(primes[index], divisorLimit); - } - } + divisorLimit = _divisorLimit; - private static bool CheckDivisors( - ulong prime, - ulong allowedMax, - Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> kernel, - AcceleratorStream stream, - MemoryBuffer1D divisorDataBuffer, - MemoryBuffer1D offsetBuffer, - MemoryBuffer1D countBuffer, - MemoryBuffer1D cycleBuffer, - MemoryBuffer1D exponentBuffer, - MemoryBuffer1D hitsBuffer, - MemoryBuffer1D hitIndexBuffer, - ulong[] divisors, - ulong[] exponents, - GpuDivisorPartialData[] divisorData, - int[] offsets, - int[] counts, - ulong[] cycles, - out ulong lastProcessed, - out bool coveredRange, - out ulong processedCount) - { - int batchCapacity = (int)divisorDataBuffer.Length; - bool composite = false; - bool processedAll = false; - processedCount = 0UL; - lastProcessed = 0UL; + for (int index = 0; index < primes.Length; index++) + { + allowedMaxValues[index] = ComputeAllowedMaxDivisorGpu(primes[index], divisorLimit); + } + } - // EvenPerfectBitScanner always configures non-empty GPU divisor buffers, so the defensive guard stays - // commented out. Tests and benchmarks that rely on zero-capacity buffers must be updated instead of re-enabling - // this branch. - // if (batchCapacity <= 0) - // { - // throw new InvalidOperationException("Divisor buffers must be non-empty."); - // } + private static bool CheckDivisors( + ulong prime, + ulong allowedMax, + Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> kernel, + AcceleratorStream stream, + MemoryBuffer1D divisorDataBuffer, + MemoryBuffer1D offsetBuffer, + MemoryBuffer1D countBuffer, + MemoryBuffer1D cycleBuffer, + MemoryBuffer1D exponentBuffer, + MemoryBuffer1D hitsBuffer, + MemoryBuffer1D hitIndexBuffer, + ulong[] divisors, + ulong[] exponents, + GpuDivisorPartialData[] divisorData, + int[] offsets, + int[] counts, + ulong[] cycles, + out ulong lastProcessed, + out bool coveredRange, + out ulong processedCount) + { + int batchCapacity = (int)divisorDataBuffer.Length; + bool composite = false; + bool processedAll = false; + processedCount = 0UL; + lastProcessed = 0UL; - int chunkCountBaseline = batchCapacity; + // EvenPerfectBitScanner always configures non-empty GPU divisor buffers, so the defensive guard stays + // commented out. Tests and benchmarks that rely on zero-capacity buffers must be updated instead of re-enabling + // this branch. + // if (batchCapacity <= 0) + // { + // throw new InvalidOperationException("Divisor buffers must be non-empty."); + // } - UInt128 twoP128 = (UInt128)prime << 1; - UInt128 allowedMax128 = allowedMax; - UInt128 firstDivisor128 = twoP128 + UInt128.One; - bool invalidStride = twoP128 == UInt128.Zero; - bool outOfRange = firstDivisor128 > allowedMax128; - UInt128 numerator = allowedMax128 - UInt128.One; - UInt128 maxK128 = invalidStride || outOfRange ? UInt128.Zero : numerator / twoP128; - bool hasCandidates = !invalidStride && !outOfRange && maxK128 != UInt128.Zero; - if (!hasCandidates) - { - coveredRange = true; - return false; - } + int chunkCountBaseline = batchCapacity; - ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128; - ulong remainingCount = maxK; + UInt128 twoP128 = (UInt128)prime << 1; + UInt128 allowedMax128 = allowedMax; + UInt128 firstDivisor128 = twoP128 + UInt128.One; + bool invalidStride = twoP128 == UInt128.Zero; + bool outOfRange = firstDivisor128 > allowedMax128; + UInt128 numerator = allowedMax128 - UInt128.One; + UInt128 maxK128 = invalidStride || outOfRange ? UInt128.Zero : numerator / twoP128; + bool hasCandidates = !invalidStride && !outOfRange && maxK128 != UInt128.Zero; + if (!hasCandidates) + { + coveredRange = true; + return false; + } - ulong stepValue = (ulong)twoP128; - byte step10 = (byte)(stepValue % 10UL); - byte step8 = (byte)(stepValue & 7UL); - byte step3 = (byte)(stepValue % 3UL); - byte step7 = (byte)(stepValue % 7UL); - byte step11 = (byte)(stepValue % 11UL); + ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128; + ulong remainingCount = maxK; - UInt128 currentDivisor128 = firstDivisor128; - ulong currentDivisorValue = (ulong)currentDivisor128; - byte remainder10 = (byte)(currentDivisorValue % 10UL); - byte remainder8 = (byte)(currentDivisorValue & 7UL); - byte remainder3 = (byte)(currentDivisorValue % 3UL); - byte remainder7 = (byte)(currentDivisorValue % 7UL); - byte remainder11 = (byte)(currentDivisorValue % 11UL); - LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; - ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit); + ulong stepValue = (ulong)twoP128; + byte step10 = (byte)(stepValue % 10UL); + byte step8 = (byte)(stepValue & 7UL); + byte step3 = (byte)(stepValue % 3UL); + byte step7 = (byte)(stepValue % 7UL); + byte step11 = (byte)(stepValue % 11UL); - Span divisorStorage = divisors.AsSpan(); - Span exponentStorage = exponents.AsSpan(); - Span divisorDataStorage = divisorData.AsSpan(); - Span offsetStorage = offsets.AsSpan(); - Span countStorage = counts.AsSpan(); - Span cycleStorage = cycles.AsSpan(); - Span divisorSpan = divisorStorage; - Span exponentSpan = exponentStorage; - Span divisorDataSpan = divisorDataStorage; - Span offsetSpan = offsetStorage; - Span countSpan = countStorage; - Span cycleSpan = cycleStorage; - ref GpuDivisorPartialData divisorDataRef = ref MemoryMarshal.GetReference(divisorDataSpan); - ref int offsetRef = ref MemoryMarshal.GetReference(offsetSpan); - ref int countRef = ref MemoryMarshal.GetReference(countSpan); - ref ulong cycleRef = ref MemoryMarshal.GetReference(cycleSpan); - ref ulong exponentRef = ref MemoryMarshal.GetReference(exponentSpan); + UInt128 currentDivisor128 = firstDivisor128; + ulong currentDivisorValue = (ulong)currentDivisor128; + byte remainder10 = (byte)(currentDivisorValue % 10UL); + byte remainder8 = (byte)(currentDivisorValue & 7UL); + byte remainder3 = (byte)(currentDivisorValue % 3UL); + byte remainder7 = (byte)(currentDivisorValue % 7UL); + byte remainder11 = (byte)(currentDivisorValue % 11UL); + LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; + ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit); - ArrayView1D divisorDataView = divisorDataBuffer.View; - ArrayView1D offsetView = offsetBuffer.View; - ArrayView1D countView = countBuffer.View; - ArrayView1D cycleView = cycleBuffer.View; - ArrayView1D exponentViewDevice = exponentBuffer.View; - ArrayView1D hitsView = hitsBuffer.View; - ArrayView1D hitIndexView = hitIndexBuffer.View; + Span divisorStorage = divisors.AsSpan(); + Span exponentStorage = exponents.AsSpan(); + Span divisorDataStorage = divisorData.AsSpan(); + Span offsetStorage = offsets.AsSpan(); + Span countStorage = counts.AsSpan(); + Span cycleStorage = cycles.AsSpan(); + Span divisorSpan = divisorStorage; + Span exponentSpan = exponentStorage; + Span divisorDataSpan = divisorDataStorage; + Span offsetSpan = offsetStorage; + Span countSpan = countStorage; + Span cycleSpan = cycleStorage; + ref GpuDivisorPartialData divisorDataRef = ref MemoryMarshal.GetReference(divisorDataSpan); + ref int offsetRef = ref MemoryMarshal.GetReference(offsetSpan); + ref int countRef = ref MemoryMarshal.GetReference(countSpan); + ref ulong cycleRef = ref MemoryMarshal.GetReference(cycleSpan); + ref ulong exponentRef = ref MemoryMarshal.GetReference(exponentSpan); - while (remainingCount > 0UL && !composite) - { - int chunkCount = chunkCountBaseline; - if ((ulong)chunkCount > remainingCount) - { - chunkCount = (int)remainingCount; - } + ArrayView1D divisorDataView = divisorDataBuffer.View; + ArrayView1D offsetView = offsetBuffer.View; + ArrayView1D countView = countBuffer.View; + ArrayView1D cycleView = cycleBuffer.View; + ArrayView1D exponentViewDevice = exponentBuffer.View; + ArrayView1D hitsView = hitsBuffer.View; + ArrayView1D hitIndexView = hitIndexBuffer.View; - UInt128 nextDivisor128 = currentDivisor128; - int admissibleCount = 0; + while (remainingCount > 0UL && !composite) + { + int chunkCount = chunkCountBaseline; + if ((ulong)chunkCount > remainingCount) + { + chunkCount = (int)remainingCount; + } - byte localRemainder10 = remainder10; - byte localRemainder8 = remainder8; - byte localRemainder3 = remainder3; - byte localRemainder7 = remainder7; - byte localRemainder11 = remainder11; + UInt128 nextDivisor128 = currentDivisor128; + int admissibleCount = 0; - for (int i = 0; i < chunkCount; i++) - { - bool passesSmallModuli = localRemainder3 != 0 && localRemainder7 != 0 && localRemainder11 != 0; - if (passesSmallModuli && (localRemainder8 == 1 || localRemainder8 == 7) && ((decimalMask >> localRemainder10) & 1) != 0) - { - ulong divisorValue = (ulong)nextDivisor128; - MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue); - ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData); - if (divisorCycle == prime) - { - processedCount += (ulong)(i + 1); - lastProcessed = divisorValue; - coveredRange = true; - return true; - } + byte localRemainder10 = remainder10; + byte localRemainder8 = remainder8; + byte localRemainder3 = remainder3; + byte localRemainder7 = remainder7; + byte localRemainder11 = remainder11; - divisorSpan[admissibleCount] = divisorValue; - divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue); - offsetSpan[admissibleCount] = admissibleCount; - countSpan[admissibleCount] = 1; - cycleSpan[admissibleCount] = divisorCycle; - exponentSpan[admissibleCount] = prime; - admissibleCount++; - } + for (int i = 0; i < chunkCount; i++) + { + bool passesSmallModuli = localRemainder3 != 0 && localRemainder7 != 0 && localRemainder11 != 0; + if (passesSmallModuli && (localRemainder8 == 1 || localRemainder8 == 7) && ((decimalMask >> localRemainder10) & 1) != 0) + { + ulong divisorValue = (ulong)nextDivisor128; + MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue); + ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData); + if (divisorCycle == prime) + { + processedCount += (ulong)(i + 1); + lastProcessed = divisorValue; + coveredRange = true; + return true; + } - nextDivisor128 += twoP128; - localRemainder10 = AddMod10(localRemainder10, step10); - localRemainder8 = AddMod8(localRemainder8, step8); - localRemainder3 = AddMod3(localRemainder3, step3); - localRemainder7 = AddMod7(localRemainder7, step7); - localRemainder11 = AddMod11(localRemainder11, step11); - } + divisorSpan[admissibleCount] = divisorValue; + divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue); + offsetSpan[admissibleCount] = admissibleCount; + countSpan[admissibleCount] = 1; + cycleSpan[admissibleCount] = divisorCycle; + exponentSpan[admissibleCount] = prime; + admissibleCount++; + } - remainder10 = localRemainder10; - remainder8 = localRemainder8; - remainder3 = localRemainder3; - remainder7 = localRemainder7; - remainder11 = localRemainder11; + nextDivisor128 += twoP128; + localRemainder10 = AddMod10(localRemainder10, step10); + localRemainder8 = AddMod8(localRemainder8, step8); + localRemainder3 = AddMod3(localRemainder3, step3); + localRemainder7 = AddMod7(localRemainder7, step7); + localRemainder11 = AddMod11(localRemainder11, step11); + } - processedCount += (ulong)chunkCount; + remainder10 = localRemainder10; + remainder8 = localRemainder8; + remainder3 = localRemainder3; + remainder7 = localRemainder7; + remainder11 = localRemainder11; - UInt128 lastDivisor128 = nextDivisor128 - twoP128; - lastProcessed = (ulong)lastDivisor128; - currentDivisor128 = nextDivisor128; - remainingCount -= (ulong)chunkCount; + processedCount += (ulong)chunkCount; - if (admissibleCount == 0) - { - continue; - } + UInt128 lastDivisor128 = nextDivisor128 - twoP128; + lastProcessed = (ulong)lastDivisor128; + currentDivisor128 = nextDivisor128; + remainingCount -= (ulong)chunkCount; - divisorDataView.CopyFromCPU(stream, ref divisorDataRef, admissibleCount); - offsetView.CopyFromCPU(stream, ref offsetRef, admissibleCount); - countView.CopyFromCPU(stream, ref countRef, admissibleCount); - cycleView.CopyFromCPU(stream, ref cycleRef, admissibleCount); - exponentViewDevice.CopyFromCPU(stream, ref exponentRef, admissibleCount); + if (admissibleCount == 0) + { + continue; + } - int sentinel = int.MaxValue; - hitIndexView.CopyFromCPU(stream, ref sentinel, 1); + divisorDataView.CopyFromCPU(stream, ref divisorDataRef, admissibleCount); + offsetView.CopyFromCPU(stream, ref offsetRef, admissibleCount); + countView.CopyFromCPU(stream, ref countRef, admissibleCount); + cycleView.CopyFromCPU(stream, ref cycleRef, admissibleCount); + exponentViewDevice.CopyFromCPU(stream, ref exponentRef, admissibleCount); - kernel(stream, new Index1D(admissibleCount), divisorDataView, offsetView, countView, exponentViewDevice, cycleView, hitsView, hitIndexView); + int sentinel = int.MaxValue; + hitIndexView.CopyFromCPU(stream, ref sentinel, 1); - int firstHit = sentinel; + kernel(stream, new Index1D(admissibleCount), divisorDataView, offsetView, countView, exponentViewDevice, cycleView, hitsView, hitIndexView); + + int firstHit = sentinel; hitIndexView.CopyToCPU(stream, ref firstHit, 1); stream.Synchronize(); - int hitIndex = firstHit >= admissibleCount ? -1 : firstHit; - bool hitFound = hitIndex >= 0; - composite = hitFound; - int lastIndex = admissibleCount - 1; - lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex]; - } + int hitIndex = firstHit >= admissibleCount ? -1 : firstHit; + bool hitFound = hitIndex >= 0; + composite = hitFound; + int lastIndex = admissibleCount - 1; + lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex]; + } - processedAll = remainingCount == 0UL; - coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128); - return composite; - } + processedAll = remainingCount == 0UL; + coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128); + return composite; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong ResolveDivisorCycle(ulong divisor, ulong prime, in MontgomeryDivisorData divisorData) - { - if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) - { - return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ResolveDivisorCycle(ulong divisor, ulong prime, in MontgomeryDivisorData divisorData) + { + if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) + { + return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); + } - return computedCycle; - } + return computedCycle; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte AddMod3(byte value, byte delta) - { - const int Modulus = 3; - int sum = value + delta; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte AddMod3(byte value, byte delta) + { + const int Modulus = 3; + int sum = value + delta; - if (sum >= Modulus) - { - sum -= Modulus; - } + if (sum >= Modulus) + { + sum -= Modulus; + } - return (byte)sum; - } + return (byte)sum; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte AddMod7(byte value, byte delta) - { - const int Modulus = 7; - int sum = value + delta; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte AddMod7(byte value, byte delta) + { + const int Modulus = 7; + int sum = value + delta; - if (sum >= Modulus) - { - sum -= Modulus; - } + if (sum >= Modulus) + { + sum -= Modulus; + } - return (byte)sum; - } + return (byte)sum; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte AddMod10(byte value, byte delta) - { - const int Modulus = 10; - int sum = value + delta; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte AddMod10(byte value, byte delta) + { + const int Modulus = 10; + int sum = value + delta; - if (sum >= Modulus) - { - sum -= Modulus; - } + if (sum >= Modulus) + { + sum -= Modulus; + } - return (byte)sum; - } + return (byte)sum; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte AddMod11(byte value, byte delta) - { - const int Modulus = 11; - int sum = value + delta; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte AddMod11(byte value, byte delta) + { + const int Modulus = 11; + int sum = value + delta; - if (sum >= Modulus) - { - sum -= Modulus; - } + if (sum >= Modulus) + { + sum -= Modulus; + } - return (byte)sum; - } + return (byte)sum; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte AddMod8(byte value, byte delta) - { - return (byte)((value + delta) & 7); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte AddMod8(byte value, byte delta) + { + return (byte)((value + delta) & 7); + } - private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime) - { - // The --mersenne=bydivisor flow in EvenPerfectBitScanner only calls ConfigureFromMaxPrime with primes greater than 1, - // so the guard below never trips in production runs. - // if (maxPrime <= 1UL) - // { - // return 0UL; - // } - if (maxPrime - 1UL >= 64UL) - { - return ulong.MaxValue; - } + private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime) + { + // The --mersenne=bydivisor flow in EvenPerfectBitScanner only calls ConfigureFromMaxPrime with primes greater than 1, + // so the guard below never trips in production runs. + // if (maxPrime <= 1UL) + // { + // return 0UL; + // } + if (maxPrime - 1UL >= 64UL) + { + return ulong.MaxValue; + } - return (1UL << (int)(maxPrime - 1UL)) - 1UL; - } + return (1UL << (int)(maxPrime - 1UL)) - 1UL; + } - private static ulong ComputeAllowedMaxDivisorGpu(ulong prime, ulong divisorLimit) - { - // Production --mersenne=bydivisor runs only pass prime exponents, so the guard below never executes outside tests. - // if (prime <= 1UL) - // { - // return 0UL; - // } - ulong cappedLimit = divisorLimit; - const ulong TestPrimeDivisorCap = 170_000_000UL; - if (prime < 10_000_000UL && cappedLimit > TestPrimeDivisorCap) - { - cappedLimit = TestPrimeDivisorCap; - } + private static ulong ComputeAllowedMaxDivisorGpu(ulong prime, ulong divisorLimit) + { + // Production --mersenne=bydivisor runs only pass prime exponents, so the guard below never executes outside tests. + // if (prime <= 1UL) + // { + // return 0UL; + // } + ulong cappedLimit = divisorLimit; + const ulong TestPrimeDivisorCap = 170_000_000UL; + if (prime < 10_000_000UL && cappedLimit > TestPrimeDivisorCap) + { + cappedLimit = TestPrimeDivisorCap; + } - if (prime - 1UL >= 64UL) - { - return cappedLimit; - } + if (prime - 1UL >= 64UL) + { + return cappedLimit; + } - ulong computedLimit = (1UL << (int)(prime - 1UL)) - 1UL; - if (prime < 10_000_000UL && computedLimit > cappedLimit) - { - computedLimit = cappedLimit; - } + ulong computedLimit = (1UL << (int)(prime - 1UL)) - 1UL; + if (prime < 10_000_000UL && computedLimit > cappedLimit) + { + computedLimit = cappedLimit; + } - return computedLimit < cappedLimit ? computedLimit : cappedLimit; - } + return computedLimit < cappedLimit ? computedLimit : cappedLimit; + } - public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession - { - private readonly MersenneNumberDivisorByDivisorGpuTester _owner; - private readonly Accelerator _accelerator; - private readonly AcceleratorStream _stream; - private Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> _kernel = null!; - private MemoryBuffer1D _divisorBuffer = null!; - private MemoryBuffer1D _offsetBuffer = null!; - private MemoryBuffer1D _countBuffer = null!; - private MemoryBuffer1D _cycleBuffer = null!; - private MemoryBuffer1D _firstHitBuffer = null!; - private MemoryBuffer1D _exponentsBuffer = null!; - private MemoryBuffer1D _hitBuffer = null!; - private ulong[] _hostBuffer = null!; - private int _capacity; + public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession + { + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; - internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) - { - _owner = owner; - // GpuPrimeWorkLimiter.Acquire(); - Accelerator accelerator = AcceleratorPool.Shared.Rent(); - _accelerator = accelerator; - _stream = accelerator.CreateStream(); - _capacity = Math.Max(1, owner._gpuBatchSize); - } + private readonly MersenneNumberDivisorByDivisorGpuTester _owner; + private readonly Accelerator _accelerator; + private readonly AcceleratorStream _stream; + private Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> _kernel = null!; + private MemoryBuffer1D _divisorBuffer = null!; + private MemoryBuffer1D _offsetBuffer = null!; + private MemoryBuffer1D _countBuffer = null!; + private MemoryBuffer1D _cycleBuffer = null!; + private MemoryBuffer1D _firstHitBuffer = null!; + private MemoryBuffer1D _exponentsBuffer = null!; + private MemoryBuffer1D _hitBuffer = null!; + private ulong[] _hostBuffer = null!; + private int _capacity; - internal void Reset() - { - } - private void EnsureExecutionResourcesLocked(int requiredCapacity) - { + internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) + { + _owner = owner; + // GpuPrimeWorkLimiter.Acquire(); + int acceleratorIndex = AcceleratorPool.Shared.Rent(); + Accelerator accelerator = _accelerators[acceleratorIndex]; + _accelerator = accelerator; + _stream = accelerator.CreateStream(); + _capacity = Math.Max(1, owner._gpuBatchSize); + } + + internal void Reset() + { + } + + private void EnsureExecutionResourcesLocked(int requiredCapacity) + { Accelerator accelerator = _accelerator; - if (_kernel == null) - { - _kernel = GetKernel(_accelerator); - } + if (_kernel == null) + { + _kernel = GetKernel(_accelerator); + } - if (_divisorBuffer == null) + if (_divisorBuffer == null) { // lock(accelerator) { @@ -537,255 +544,255 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv _offsetBuffer = accelerator.Allocate1D(1); _countBuffer = accelerator.Allocate1D(1); _cycleBuffer = accelerator.Allocate1D(1); - _firstHitBuffer = accelerator.Allocate1D(1); + _firstHitBuffer = accelerator.Allocate1D(1); } - } + } - bool allocateBuffers = _exponentsBuffer == null; - if (!allocateBuffers && _capacity < requiredCapacity) - { - _exponentsBuffer?.Dispose(); - _hitBuffer?.Dispose(); - if (_hostBuffer != null) - { - ThreadStaticPools.UlongPool.Return(_hostBuffer, clearArray: false); - _hostBuffer = null!; - } + bool allocateBuffers = _exponentsBuffer == null; + if (!allocateBuffers && _capacity < requiredCapacity) + { + _exponentsBuffer?.Dispose(); + _hitBuffer?.Dispose(); + if (_hostBuffer != null) + { + ThreadStaticPools.UlongPool.Return(_hostBuffer, clearArray: false); + _hostBuffer = null!; + } - _exponentsBuffer = null!; - _hitBuffer = null!; - allocateBuffers = true; - } + _exponentsBuffer = null!; + _hitBuffer = null!; + allocateBuffers = true; + } - if (allocateBuffers) - { - int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity; + if (allocateBuffers) + { + int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity; _capacity = desiredCapacity; // lock (accelerator) { _exponentsBuffer = accelerator.Allocate1D(desiredCapacity); _hitBuffer = accelerator.Allocate1D(desiredCapacity); } - - _hostBuffer = ThreadStaticPools.UlongPool.Rent(desiredCapacity); - } - } - public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan primes, Span hits) - { - int length = primes.Length; - // EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the guard stays commented out. - // if (length == 0) - // { - // return; - // } + _hostBuffer = ThreadStaticPools.UlongPool.Rent(desiredCapacity); + } + } - // The GPU divisor sessions only materialize odd moduli greater than one (q = 2kp + 1), - // so the defensive modulus guard stays commented out to keep the hot path branch-free. - // if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL) - // { - // hits.Clear(); - // return; - // } + public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan primes, Span hits) + { + int length = primes.Length; + // EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the guard stays commented out. + // if (length == 0) + // { + // return; + // } - ulong cycle = divisorCycle; - ulong firstPrime = primes[0]; + // The GPU divisor sessions only materialize odd moduli greater than one (q = 2kp + 1), + // so the defensive modulus guard stays commented out to keep the hot path branch-free. + // if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL) + // { + // hits.Clear(); + // return; + // } - if (cycle == 0UL) - { - if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) - { - cycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); - } - else - { - cycle = computedCycle; - } + ulong cycle = divisorCycle; + ulong firstPrime = primes[0]; - if (cycle == 0UL) - { - throw new InvalidOperationException("GPU divisor cycle length must be non-zero."); - } - } + if (cycle == 0UL) + { + if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) + { + cycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); + } + else + { + cycle = computedCycle; + } - // Monitor.Enter(_lease.ExecutionLock); + if (cycle == 0UL) + { + throw new InvalidOperationException("GPU divisor cycle length must be non-zero."); + } + } - EnsureExecutionResourcesLocked(length); + // Monitor.Enter(_lease.ExecutionLock); - ArrayView1D divisorView = _divisorBuffer.View; - ArrayView1D offsetView = _offsetBuffer.View; - ArrayView1D countView = _countBuffer.View; - ArrayView1D cycleView = _cycleBuffer.View; - ArrayView1D firstHitView = _firstHitBuffer.View; - ArrayView1D exponentView = _exponentsBuffer.View; - ArrayView1D hitView = _hitBuffer.View; + EnsureExecutionResourcesLocked(length); + + ArrayView1D divisorView = _divisorBuffer.View; + ArrayView1D offsetView = _offsetBuffer.View; + ArrayView1D countView = _countBuffer.View; + ArrayView1D cycleView = _cycleBuffer.View; + ArrayView1D firstHitView = _firstHitBuffer.View; + ArrayView1D exponentView = _exponentsBuffer.View; + ArrayView1D hitView = _hitBuffer.View; AcceleratorStream stream = _stream; GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor); - divisorView.CopyFromCPU(stream, ref partialData, 1); + divisorView.CopyFromCPU(stream, ref partialData, 1); - int offsetValue = 0; - offsetView.CopyFromCPU(stream, ref offsetValue, 1); + int offsetValue = 0; + offsetView.CopyFromCPU(stream, ref offsetValue, 1); - int countValue = length; - countView.CopyFromCPU(stream, ref countValue, 1); + int countValue = length; + countView.CopyFromCPU(stream, ref countValue, 1); - ulong cycleValue = cycle; - cycleView.CopyFromCPU(stream, ref cycleValue, 1); + ulong cycleValue = cycle; + cycleView.CopyFromCPU(stream, ref cycleValue, 1); - Span hostSpan = _hostBuffer.AsSpan(0, length); - primes.CopyTo(hostSpan); - ref ulong hostRef = ref MemoryMarshal.GetReference(hostSpan); + Span hostSpan = _hostBuffer.AsSpan(0, length); + primes.CopyTo(hostSpan); + ref ulong hostRef = ref MemoryMarshal.GetReference(hostSpan); - exponentView.CopyFromCPU(stream, ref hostRef, length); + exponentView.CopyFromCPU(stream, ref hostRef, length); - int sentinel = int.MaxValue; - firstHitView.CopyFromCPU(stream, ref sentinel, 1); + int sentinel = int.MaxValue; + firstHitView.CopyFromCPU(stream, ref sentinel, 1); - var kernel = _kernel!; + var kernel = _kernel!; - kernel(stream, new Index1D(1), divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView); + kernel(stream, new Index1D(1), divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView); - Span hitSlice = hits.Slice(0, length); - ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice); + Span hitSlice = hits.Slice(0, length); + ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice); hitView.CopyToCPU(stream, ref hitRef, length); stream.Synchronize(); - // Monitor.Exit(_lease.ExecutionLock); - } + // Monitor.Exit(_lease.ExecutionLock); + } - public void Dispose() - { - _owner._sessionPool.Add(this); - } - } + public void Dispose() + { + _owner._sessionPool.Add(this); + } + } - public ulong DivisorLimit - { - get - { - // The EvenPerfectBitScanner driver configures the GPU tester before exposing it to callers, so the previous synchronization guard remains commented out. - // lock (_sync) - // { - // if (!_isConfigured) - // { - // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); - // } + public ulong DivisorLimit + { + get + { + // The EvenPerfectBitScanner driver configures the GPU tester before exposing it to callers, so the previous synchronization guard remains commented out. + // lock (_sync) + // { + // if (!_isConfigured) + // { + // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + // } - // return _divisorLimit; - // } + // return _divisorLimit; + // } - return _divisorLimit; - } - } + return _divisorLimit; + } + } - private BatchResources RentBatchResources(Accelerator accelerator, int capacity) - { - if (!_resourcePool.TryTake(out BatchResources? resources)) - { - resources = new BatchResources(Math.Max(1, capacity)); - } + private BatchResources RentBatchResources(Accelerator accelerator, int capacity) + { + if (!_resourcePool.TryTake(out BatchResources? resources)) + { + resources = new BatchResources(Math.Max(1, capacity)); + } - resources.Bind(accelerator, capacity); - return resources; - } + resources.Bind(accelerator, capacity); + return resources; + } - private void ReturnBatchResources(BatchResources resources) => _resourcePool.Add(resources); + private void ReturnBatchResources(BatchResources resources) => _resourcePool.Add(resources); - private sealed class BatchResources - { - private Accelerator? _accelerator; - private MemoryBuffer1D? _divisorDataBuffer; - private MemoryBuffer1D? _offsetBuffer; - private MemoryBuffer1D? _countBuffer; - private MemoryBuffer1D? _cycleBuffer; - private MemoryBuffer1D? _exponentBuffer; - private MemoryBuffer1D? _hitsBuffer; - private MemoryBuffer1D? _hitIndexBuffer; - private int _deviceCapacity; - private int _hostCapacity; + private sealed class BatchResources + { + private Accelerator? _accelerator; + private MemoryBuffer1D? _divisorDataBuffer; + private MemoryBuffer1D? _offsetBuffer; + private MemoryBuffer1D? _countBuffer; + private MemoryBuffer1D? _cycleBuffer; + private MemoryBuffer1D? _exponentBuffer; + private MemoryBuffer1D? _hitsBuffer; + private MemoryBuffer1D? _hitIndexBuffer; + private int _deviceCapacity; + private int _hostCapacity; - internal BatchResources(int capacity) - { - int actualCapacity = Math.Max(1, capacity); - RentHostArrays(actualCapacity); - } + internal BatchResources(int capacity) + { + int actualCapacity = Math.Max(1, capacity); + RentHostArrays(actualCapacity); + } - internal MemoryBuffer1D DivisorDataBuffer => _divisorDataBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); + internal MemoryBuffer1D DivisorDataBuffer => _divisorDataBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); - internal MemoryBuffer1D OffsetBuffer => _offsetBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); + internal MemoryBuffer1D OffsetBuffer => _offsetBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); - internal MemoryBuffer1D CountBuffer => _countBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); + internal MemoryBuffer1D CountBuffer => _countBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); - internal MemoryBuffer1D CycleBuffer => _cycleBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); + internal MemoryBuffer1D CycleBuffer => _cycleBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); - internal MemoryBuffer1D ExponentBuffer => _exponentBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); + internal MemoryBuffer1D ExponentBuffer => _exponentBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); - internal MemoryBuffer1D HitsBuffer => _hitsBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); + internal MemoryBuffer1D HitsBuffer => _hitsBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); - internal MemoryBuffer1D HitIndexBuffer => _hitIndexBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); + internal MemoryBuffer1D HitIndexBuffer => _hitIndexBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); - internal ulong[] Divisors { get; private set; } = null!; + internal ulong[] Divisors { get; private set; } = null!; - internal ulong[] Exponents { get; private set; } = null!; + internal ulong[] Exponents { get; private set; } = null!; - internal GpuDivisorPartialData[] DivisorData { get; private set; } = null!; + internal GpuDivisorPartialData[] DivisorData { get; private set; } = null!; - internal int[] Offsets { get; private set; } = null!; + internal int[] Offsets { get; private set; } = null!; - internal int[] Counts { get; private set; } = null!; + internal int[] Counts { get; private set; } = null!; - internal ulong[] Cycles { get; private set; } = null!; + internal ulong[] Cycles { get; private set; } = null!; - internal int Capacity => _hostCapacity; + internal int Capacity => _hostCapacity; - internal void Bind(Accelerator accelerator, int requiredCapacity) - { - EnsureHostCapacity(requiredCapacity); + internal void Bind(Accelerator accelerator, int requiredCapacity) + { + EnsureHostCapacity(requiredCapacity); - if (!ReferenceEquals(_accelerator, accelerator)) - { - ReleaseDeviceBuffers(); - _accelerator = accelerator; - } + if (!ReferenceEquals(_accelerator, accelerator)) + { + ReleaseDeviceBuffers(); + _accelerator = accelerator; + } - EnsureDeviceCapacity(accelerator, requiredCapacity); - } + EnsureDeviceCapacity(accelerator, requiredCapacity); + } - private void EnsureHostCapacity(int requiredCapacity) - { - int desiredCapacity = Math.Max(1, requiredCapacity); - if (_hostCapacity >= desiredCapacity) - { - return; - } + private void EnsureHostCapacity(int requiredCapacity) + { + int desiredCapacity = Math.Max(1, requiredCapacity); + if (_hostCapacity >= desiredCapacity) + { + return; + } - ReturnHostArrays(); - RentHostArrays(desiredCapacity); - } + ReturnHostArrays(); + RentHostArrays(desiredCapacity); + } - private void EnsureDeviceCapacity(Accelerator accelerator, int requiredCapacity) - { - int desiredCapacity = Math.Max(1, requiredCapacity); - if (_divisorDataBuffer is null) - { - AllocateDeviceBuffers(accelerator, desiredCapacity); - return; - } + private void EnsureDeviceCapacity(Accelerator accelerator, int requiredCapacity) + { + int desiredCapacity = Math.Max(1, requiredCapacity); + if (_divisorDataBuffer is null) + { + AllocateDeviceBuffers(accelerator, desiredCapacity); + return; + } - if (_deviceCapacity >= desiredCapacity) - { - return; - } + if (_deviceCapacity >= desiredCapacity) + { + return; + } - ReleaseDeviceBuffers(); - AllocateDeviceBuffers(accelerator, desiredCapacity); - } + ReleaseDeviceBuffers(); + AllocateDeviceBuffers(accelerator, desiredCapacity); + } - private void AllocateDeviceBuffers(Accelerator accelerator, int capacity) + private void AllocateDeviceBuffers(Accelerator accelerator, int capacity) { // lock (accelerator) { @@ -797,129 +804,129 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv _hitsBuffer = accelerator.Allocate1D(capacity); _hitIndexBuffer = accelerator.Allocate1D(1); } - - _deviceCapacity = capacity; - } - private void RentHostArrays(int capacity) - { - ArrayPool ulongPool = ThreadStaticPools.UlongPool; - ArrayPool intPool = ThreadStaticPools.IntPool; - ArrayPool partialPool = ThreadStaticPools.GpuDivisorPartialDataPool; + _deviceCapacity = capacity; + } - Divisors = ulongPool.Rent(capacity); - Exponents = ulongPool.Rent(capacity); - DivisorData = partialPool.Rent(capacity); - Offsets = intPool.Rent(capacity); - Counts = intPool.Rent(capacity); - Cycles = ulongPool.Rent(capacity); - _hostCapacity = capacity; - } + private void RentHostArrays(int capacity) + { + ArrayPool ulongPool = ThreadStaticPools.UlongPool; + ArrayPool intPool = ThreadStaticPools.IntPool; + ArrayPool partialPool = ThreadStaticPools.GpuDivisorPartialDataPool; - private void ReleaseDeviceBuffers() - { - _divisorDataBuffer?.Dispose(); - _offsetBuffer?.Dispose(); - _countBuffer?.Dispose(); - _cycleBuffer?.Dispose(); - _exponentBuffer?.Dispose(); - _hitsBuffer?.Dispose(); - _hitIndexBuffer?.Dispose(); - _divisorDataBuffer = null; - _offsetBuffer = null; - _countBuffer = null; - _cycleBuffer = null; - _exponentBuffer = null; - _hitsBuffer = null; - _hitIndexBuffer = null; - _deviceCapacity = 0; - } + Divisors = ulongPool.Rent(capacity); + Exponents = ulongPool.Rent(capacity); + DivisorData = partialPool.Rent(capacity); + Offsets = intPool.Rent(capacity); + Counts = intPool.Rent(capacity); + Cycles = ulongPool.Rent(capacity); + _hostCapacity = capacity; + } - private void ReturnHostArrays() - { - if (_hostCapacity == 0) - { - return; - } + private void ReleaseDeviceBuffers() + { + _divisorDataBuffer?.Dispose(); + _offsetBuffer?.Dispose(); + _countBuffer?.Dispose(); + _cycleBuffer?.Dispose(); + _exponentBuffer?.Dispose(); + _hitsBuffer?.Dispose(); + _hitIndexBuffer?.Dispose(); + _divisorDataBuffer = null; + _offsetBuffer = null; + _countBuffer = null; + _cycleBuffer = null; + _exponentBuffer = null; + _hitsBuffer = null; + _hitIndexBuffer = null; + _deviceCapacity = 0; + } - ArrayPool ulongPool = ThreadStaticPools.UlongPool; - ArrayPool intPool = ThreadStaticPools.IntPool; - ArrayPool partialPool = ThreadStaticPools.GpuDivisorPartialDataPool; - ulongPool.Return(Divisors, clearArray: false); - ulongPool.Return(Exponents, clearArray: false); - partialPool.Return(DivisorData, clearArray: false); - intPool.Return(Offsets, clearArray: false); - intPool.Return(Counts, clearArray: false); - ulongPool.Return(Cycles, clearArray: false); - Divisors = Array.Empty(); - Exponents = Array.Empty(); - DivisorData = Array.Empty(); - Offsets = Array.Empty(); - Counts = Array.Empty(); - Cycles = Array.Empty(); - _hostCapacity = 0; - } + private void ReturnHostArrays() + { + if (_hostCapacity == 0) + { + return; + } - public void Dispose() - { - ReleaseDeviceBuffers(); - _accelerator = null; - ReturnHostArrays(); - } - } + ArrayPool ulongPool = ThreadStaticPools.UlongPool; + ArrayPool intPool = ThreadStaticPools.IntPool; + ArrayPool partialPool = ThreadStaticPools.GpuDivisorPartialDataPool; + ulongPool.Return(Divisors, clearArray: false); + ulongPool.Return(Exponents, clearArray: false); + partialPool.Return(DivisorData, clearArray: false); + intPool.Return(Offsets, clearArray: false); + intPool.Return(Counts, clearArray: false); + ulongPool.Return(Cycles, clearArray: false); + Divisors = Array.Empty(); + Exponents = Array.Empty(); + DivisorData = Array.Empty(); + Offsets = Array.Empty(); + Counts = Array.Empty(); + Cycles = Array.Empty(); + _hostCapacity = 0; + } - private sealed class AcceleratorReferenceComparer : IEqualityComparer - { - public static AcceleratorReferenceComparer Instance { get; } = new(); + public void Dispose() + { + ReleaseDeviceBuffers(); + _accelerator = null; + ReturnHostArrays(); + } + } - public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y); + private sealed class AcceleratorReferenceComparer : IEqualityComparer + { + public static AcceleratorReferenceComparer Instance { get; } = new(); - public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj); - } + public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y); - 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."); - // } + public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj); + } - // return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); - // } + 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); + // } - public DivisorScanSession CreateDivisorSession() - { - // The tester is configured once at startup and the session pool relies on thread-safe collections, so the previous synchronization guard stays commented out. - // lock (_sync) - // { - // if (!_isConfigured) - // { - // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); - // } + return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); + } - // if (_sessionPool.TryTake(out DivisorScanSession? session)) - // { - // session.Reset(); - // return session; - // } + public DivisorScanSession CreateDivisorSession() + { + // The tester is configured once at startup and the session pool relies on thread-safe collections, so the previous synchronization guard stays commented out. + // lock (_sync) + // { + // if (!_isConfigured) + // { + // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + // } - // return new DivisorScanSession(this); - // } + // if (_sessionPool.TryTake(out DivisorScanSession? session)) + // { + // session.Reset(); + // return session; + // } - if (_sessionPool.TryTake(out DivisorScanSession? session)) - { - session.Reset(); - return session; - } + // return new DivisorScanSession(this); + // } - return new DivisorScanSession(this); - } + if (_sessionPool.TryTake(out DivisorScanSession? session)) + { + session.Reset(); + return session; + } - IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); + return new DivisorScanSession(this); + } + + IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); } \ No newline at end of file diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs index 630d655..6a25fef 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs @@ -54,10 +54,13 @@ public sealed class MersenneNumberDivisorGpuTester _divisorCandidates = count == 0 ? [] : list[..count]; } + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor) { // var accelerator = SharedGpuContext.CreateAccelerator(); - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); Queue> resultBuffers = GetQueue(accelerator); if (!resultBuffers.TryDequeue(out var resultBuffer)) diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs index c856f13..c5903ce 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs @@ -12,12 +12,15 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u private readonly GpuKernelType _kernelType = kernelType; private readonly bool _useGpuOrder = useGpuOrder; + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder})."); // GpuPrimeWorkLimiter.Acquire(); - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy UInt128 kStart = 1UL; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs index a072f56..9d64c3b 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs @@ -160,7 +160,8 @@ public class MersenneNumberLucasLehmerGpuTester else { // GpuPrimeWorkLimiter.Acquire(); - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch. var buffer = accelerator.Allocate1D(1); @@ -193,7 +194,8 @@ public class MersenneNumberLucasLehmerGpuTester } } - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var expBuffer = accelerator.Allocate1D(count); @@ -246,9 +248,12 @@ public class MersenneNumberLucasLehmerGpuTester gpuUInt128Pool.Return(buffer); } + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu) { - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var subKernel = GetSubSmallKernel(accelerator); diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs index e385ff6..025af5e 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs @@ -7,11 +7,14 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO private readonly GpuKernelType _kernelType = kernelType; private readonly bool _useGpuOrder = useGpuOrder; + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { // GpuPrimeWorkLimiter.Acquire(); // var accelerator = SharedGpuContext.Accelerator; - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR UInt128 kStart = UInt128.One; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index 1f6a8ad..4543832 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -22,11 +22,14 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) // TODO: When cycles are missing for larger q values, compute only the single required cycle on the device // requested by the caller, skip queuing additional block generation, and keep the snapshot cache untouched. + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { // GpuPrimeWorkLimiter.Acquire(); - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; // Monitor.Enter(gpuLease.ExecutionLock); diff --git a/PerfectNumbers.Core/Gpu/NttGpuMath.cs b/PerfectNumbers.Core/Gpu/NttGpuMath.cs index 1ef9301..b01876f 100644 --- a/PerfectNumbers.Core/Gpu/NttGpuMath.cs +++ b/PerfectNumbers.Core/Gpu/NttGpuMath.cs @@ -574,7 +574,8 @@ public static class NttGpuMath { int n = values.Length; int bits = (int)Math.Log2(n); - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var pool = ThreadStaticPools.GpuUInt128Pool; var array = pool.Rent(n); values.CopyTo(array); @@ -636,7 +637,8 @@ public static class NttGpuMath private static void ForwardGpuReference(Span values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) { int n = (int)values.Length; - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var pool = ThreadStaticPools.GpuUInt128Pool; var inputArray = pool.Rent(n); @@ -732,7 +734,8 @@ public static class NttGpuMath private static void InverseGpuReference(Span values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) { int n = values.Length; - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var pool = ThreadStaticPools.GpuUInt128Pool; var inputArray = pool.Rent(n); @@ -780,7 +783,8 @@ public static class NttGpuMath private static void ForwardGpuStaged(Span values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) { int n = values.Length; - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var buffer = accelerator.Allocate1D(n); buffer.View.CopyFromCPU(stream, ref values[0], n); @@ -795,7 +799,8 @@ public static class NttGpuMath private static void InverseGpuStaged(Span values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) { int n = values.Length; - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var buffer = accelerator.Allocate1D(n); buffer.View.CopyFromCPU(stream, ref values[0], n); @@ -998,9 +1003,12 @@ public static class NttGpuMath InverseDevice(accelerator, stream, values, (int)values.Length, modulus, cache); } + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + public static void PointwiseMultiply(Span left, Span right, GpuUInt128 modulus) { - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var pool = ThreadStaticPools.GpuUInt128Pool; var leftArray = pool.Rent(left.Length); diff --git a/PerfectNumbers.Core/Gpu/Pow2MontgomeryGpuCalculator.cs b/PerfectNumbers.Core/Gpu/Pow2MontgomeryGpuCalculator.cs index a11c74a..9ecc739 100644 --- a/PerfectNumbers.Core/Gpu/Pow2MontgomeryGpuCalculator.cs +++ b/PerfectNumbers.Core/Gpu/Pow2MontgomeryGpuCalculator.cs @@ -14,12 +14,13 @@ public static class Pow2MontgomeryGpuCalculator ulong result = 0UL; var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + int acceleratorIndex = gpu.AcceleratorIndex; Accelerator? accelerator = gpu.Accelerator; MemoryBuffer1D exponentBuffer = gpu.Input; MemoryBuffer1D resultBuffer = gpu.OutputUlong; var kernel = gpu.KeepMontgomeryKernel; - AcceleratorStream stream = AcceleratorStreamPool.Rent(accelerator); + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1); // We don't need to worry about any left-overs here. @@ -31,7 +32,7 @@ public static class Pow2MontgomeryGpuCalculator resultBuffer.View.CopyToCPU(stream, ref result, 1); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); // GpuPrimeWorkLimiter.Release(); PrimeOrderCalculatorAccelerator.Return(gpu); @@ -48,7 +49,8 @@ public static class Pow2MontgomeryGpuCalculator MemoryBuffer1D exponentBuffer = gpu.Input; MemoryBuffer1D resultBuffer = gpu.OutputUlong; - AcceleratorStream stream = AcceleratorStreamPool.Rent(gpu.Accelerator); + int acceleratorIndex = gpu.AcceleratorIndex; + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1); // We don't need to worry about any left-overs here. // resultBuffer.MemSetToZero(stream); @@ -58,7 +60,7 @@ public static class Pow2MontgomeryGpuCalculator resultBuffer.View.CopyToCPU(stream, ref result, 1); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); // GpuPrimeWorkLimiter.Release(); PrimeOrderCalculatorAccelerator.Return(gpu); @@ -73,7 +75,8 @@ public static class Pow2MontgomeryGpuCalculator MemoryBuffer1D resultBuffer = gpu.OutputUlong; var kernel = gpu.ConvertToStandardKernel; - AcceleratorStream stream = AcceleratorStreamPool.Rent(gpu.Accelerator); + int acceleratorIndex = gpu.AcceleratorIndex; + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1); // We don't need to worry about any left-overs here. // resultBuffer.MemSetToZero(stream); @@ -83,7 +86,7 @@ public static class Pow2MontgomeryGpuCalculator resultBuffer.View.CopyToCPU(stream, ref result, 1); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); return result; } diff --git a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs index e113f8f..c955265 100644 --- a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs +++ b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs @@ -205,6 +205,8 @@ internal static partial class PrimeOrderGpuHeuristics return true; } + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + private static bool TryLaunchPartialFactorKernel( PrimeOrderCalculatorAccelerator gpu, ulong value, @@ -220,7 +222,8 @@ internal static partial class PrimeOrderGpuHeuristics try { // GpuPrimeWorkLimiter.Acquire(); - Accelerator accelerator = AcceleratorPool.Shared.Rent(); + int acceleratorIndex = AcceleratorPool.Shared.Rent(); + Accelerator accelerator = _accelerators[acceleratorIndex]; AcceleratorStream stream = accelerator.CreateStream(); SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator, stream); @@ -382,8 +385,9 @@ internal static partial class PrimeOrderGpuHeuristics order = 0UL; // GpuPrimeWorkLimiter.Acquire(); - Accelerator accelerator = AcceleratorPool.Shared.Rent(); - AcceleratorStream stream = AcceleratorStreamPool.Rent(accelerator); + int acceleratorIndex = AcceleratorPool.Shared.Rent(); + Accelerator accelerator = _accelerators[acceleratorIndex]; + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator, stream); // TODO: These buffers should be created reallocated once and assigned to an accelerator. Callers should use their own @@ -553,7 +557,8 @@ internal static partial class PrimeOrderGpuHeuristics private static bool TryComputeOnGpu(ReadOnlySpan exponents, ulong prime, in MontgomeryDivisorData divisorData, Span results) { // GpuPrimeWorkLimiter.Acquire(); - Accelerator accelerator = AcceleratorPool.Shared.Rent(); + int acceleratorIndex = AcceleratorPool.Shared.Rent(); + Accelerator accelerator = _accelerators[acceleratorIndex]; AcceleratorStream? stream = accelerator.CreateStream(); // TODO: These buffers should be allocated once per accelerator and only reallocated when the new length exceeds capacity. // Modify the callers to use their own pool of buffers per accelerator, so that other threads don't use the accelerators @@ -630,19 +635,20 @@ internal static partial class PrimeOrderGpuHeuristics GpuUInt128 modulus = (GpuUInt128)prime; gpu = PrimeOrderCalculatorAccelerator.Rent(1); + int acceleratorIndex = gpu.AcceleratorIndex; Accelerator accelerator = gpu.Accelerator; MemoryBuffer1D exponentBuffer = accelerator.Allocate1D(length); MemoryBuffer1D remainderBuffer = accelerator.Allocate1D(length); - AcceleratorStream? stream = AcceleratorStreamPool.Rent(accelerator); + AcceleratorStream? stream = AcceleratorStreamPool.Rent(acceleratorIndex); exponentBuffer.View.CopyFromCPU(stream, exponentSpan); gpu.Pow2ModWideKernel.Launch(stream, length, exponentBuffer.View, modulus, remainderBuffer.View); remainderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(resultSpan), length); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); for (int i = 0; i < length; i++) { results[i] = (UInt128)resultSpan[i]; diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 7594897..75e85c1 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -791,6 +791,8 @@ public class MersenneDivisorCycles [ThreadStatic] private static Dictionary, ArrayView1D>>? _divisorCycleKernel; + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + public static void GenerateGpu(string path, ulong maxDivisor, int batchSize = 1_000_000, long skipCount = 0L, long nextPosition = 0L) { // Prepare output file with header @@ -805,7 +807,8 @@ public class MersenneDivisorCycles divisors = pool.Rent(count); outCycles = pool.Rent(count); // GpuPrimeWorkLimiter.Acquire(); - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); using Stream outputStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, BufferSize10M, useAsync: true); diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index eab4264..d82c012 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -49,6 +49,8 @@ public sealed class MersenneNumberTester( // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags. // Also consider injecting a shared cycles cache (MersenneDivisorCycles.Shared) to both CPU and GPU testers. + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; + public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL) { bool cacheEnabled = useOrderCache; @@ -148,7 +150,8 @@ public sealed class MersenneNumberTester( UInt128[] qs = qsBuffer; // GpuPrimeWorkLimiter.Acquire(); - var accelerator = AcceleratorPool.Shared.Rent(); + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; var stream = accelerator.CreateStream(); var orderKernel = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.OrderKernelScan).Order!; diff --git a/PerfectNumbers.Core/PerfectNumberConstants.cs b/PerfectNumbers.Core/PerfectNumberConstants.cs index aefa2c1..ef99aff 100644 --- a/PerfectNumbers.Core/PerfectNumberConstants.cs +++ b/PerfectNumbers.Core/PerfectNumberConstants.cs @@ -14,8 +14,8 @@ public static class PerfectNumberConstants public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000; public const int MaxOddPowersCount = 128; public const int PooledArrayThreshold = 64; - public static readonly int RollingAccelerators = 256; //SharedGpuContext.Device.MaxNumThreadsPerGroup; - public const int ThreadsByStream = 1; + public static readonly int RollingAccelerators = 298; //SharedGpuContext.Device.MaxNumThreadsPerGroup; + public const int ThreadsByAccelerator = 4096; // TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal // divisor-cycle datasets we generate offline. // TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 597e69e..0dabed6 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -65,7 +65,10 @@ internal static partial class PrimeOrderCalculator ulong result; if (phiFactors.Factors is null) { - result = CalculateByFactorizationGpu(gpu, prime, divisorData); + result = prime <= PerfectNumberConstants.MaxQForDivisorCycles + ? CalculateByFactorizationGpu(gpu, prime, divisorData) + : CalculateByFactorizationCpu(gpu, prime, divisorData); + phiFactors.Dispose(); PrimeOrderCalculatorAccelerator.Return(gpu); return result; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs index b09f935..53f1850 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs @@ -36,11 +36,12 @@ internal static partial class PrimeOrderCalculator entries.Sort(entries, static (a, b) => a.Key.CompareTo(b.Key)); ulong order = phi; + var acceleratorIndex = gpu.AcceleratorIndex; var kernel = gpu.CheckFactorsKernel; gpu.EnsureCapacity(entryCount, 1); // GpuPrimeWorkLimiter.Acquire(); - AcceleratorStream stream = AcceleratorStreamPool.Rent(gpu.Accelerator); + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); gpu.Pow2ModEntriesToTestOnDevice.View.CopyFromCPU(stream, entries); var kernelLauncher = kernel.CreateLauncherDelegate, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D>>(); @@ -49,7 +50,7 @@ internal static partial class PrimeOrderCalculator gpu.OutputUlong.View.CopyToCPU(stream, ref order, 1); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); // GpuPrimeWorkLimiter.Release(); return order; @@ -64,10 +65,11 @@ internal static partial class PrimeOrderCalculator remaining = value; gpu.EnsureSmallPrimeFactorSlotsCapacity(GpuSmallPrimeFactorSlots); + int acceleratorIndex = gpu.AcceleratorIndex; var accelerator = gpu.Accelerator; // GpuPrimeWorkLimiter.Acquire(); - var stream = AcceleratorStreamPool.Rent(accelerator); + var stream = AcceleratorStreamPool.Rent(acceleratorIndex); KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.SmallPrimeFactorKernelScan); SmallPrimeFactorViews tables = GpuKernelPool.GetSmallPrimeFactorTables(kernels); @@ -112,7 +114,7 @@ internal static partial class PrimeOrderCalculator smallPrimeFactorRemainingSlotView.CopyToCPU(stream, ref remaining, 1); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); // GpuPrimeWorkLimiter.Release(); for (int i = 0; i < factorCount; i++) @@ -135,9 +137,10 @@ internal static partial class PrimeOrderCalculator int factorCount = factors.Length; gpu.EnsureSpecialMaxFactorsCapacity(factorCount); + int acceleratorIndex = gpu.AcceleratorIndex; Accelerator accelerator = gpu.Accelerator; // GpuPrimeWorkLimiter.Acquire(); - var stream = AcceleratorStreamPool.Rent(accelerator); + var stream = AcceleratorStreamPool.Rent(acceleratorIndex); var kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.EvaluateSpecialMaxCandidatesKernel); ArrayView1D specialMaxFactorsView = gpu.SpecialMaxFactors.View; @@ -161,7 +164,7 @@ internal static partial class PrimeOrderCalculator specialMaxResultView.CopyToCPU(stream, ref result, 1); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); // GpuPrimeWorkLimiter.Release(); return result != 0; } @@ -174,7 +177,8 @@ internal static partial class PrimeOrderCalculator Span factoredSpan = stackalloc byte[1]; Span factorSpan = stackalloc ulong[1]; - AcceleratorStream stream = AcceleratorStreamPool.Rent(gpu.Accelerator); + int acceleratorIndex = gpu.AcceleratorIndex; + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); gpu.Input.View.CopyFromCPU(stream, randomStateSpan); var kernelLauncher = gpu.PollardRhoKernel.CreateLauncherDelegate, ArrayView1D, ArrayView1D>>(); @@ -186,7 +190,7 @@ internal static partial class PrimeOrderCalculator gpu.Input.View.CopyToCPU(stream, randomStateSpan); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); ThreadStaticDeterministicRandomGpu.Exclusive.SetState(randomStateSpan[0]); bool factored = factoredSpan[0] != 0; diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index bc5ccd5..1fabf41 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -95,12 +95,13 @@ public sealed class PrimeTester byte flag = 0; var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + int acceleratorIndex = gpu.AcceleratorIndex; var inputView = gpu.PrimeTestInput.View; var outputView = gpu.OutputByte.View; var kernel = gpu.SmallPrimeSieveKernel!; // GpuPrimeWorkLimiter.Acquire(); - AcceleratorStream stream = AcceleratorStreamPool.Rent(gpu.Accelerator); + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); inputView.CopyFromCPU(stream, ref n, 1); var kernelLauncher = kernel.CreateLauncherDelegate, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>>(); @@ -122,7 +123,7 @@ public sealed class PrimeTester outputView.CopyToCPU(stream, ref flag, 1); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); PrimeOrderCalculatorAccelerator.Return(gpu); // GpuPrimeWorkLimiter.Release(); @@ -139,7 +140,8 @@ public sealed class PrimeTester var outputView = output.View; Kernel kernel = gpu.SmallPrimeSieveKernel!; - AcceleratorStream stream = AcceleratorStreamPool.Rent(gpu.Accelerator); + int acceleratorIndex = gpu.AcceleratorIndex; + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); inputView.CopyFromCPU(stream, ref n, 1); var kernelLauncher = kernel.CreateLauncherDelegate, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>>(); @@ -160,7 +162,7 @@ public sealed class PrimeTester outputView.CopyToCPU(stream, ref flag, 1); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); return flag != 0; } @@ -195,6 +197,7 @@ public sealed class PrimeTester { // GpuPrimeWorkLimiter.Acquire(); var gpu = PrimeOrderCalculatorAccelerator.Rent(GpuBatchSize); + int acceleratorIndex = gpu.AcceleratorIndex; var accelerator = gpu.Accelerator; var kernel = gpu.SmallPrimeSieveKernel!; int totalLength = values.Length; @@ -206,7 +209,7 @@ public sealed class PrimeTester var outputView = output.View; int pos = 0; - AcceleratorStream stream = AcceleratorStreamPool.Rent(accelerator); + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); while (pos < totalLength) { int remaining = totalLength - pos; @@ -236,7 +239,7 @@ public sealed class PrimeTester } stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); PrimeOrderCalculatorAccelerator.Return(gpu); // GpuPrimeWorkLimiter.Release(); } @@ -280,13 +283,14 @@ public sealed class PrimeTester int length = values.Length; var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + int acceleratorIndex = gpu.AcceleratorIndex; var accelerator = gpu.Accelerator; var kernel = gpu.SharesFactorKernel; gpu.EnsureCapacity(0, length); MemoryBuffer1D? inputBuffer = gpu.PrimeTestInput; MemoryBuffer1D? resultBuffer = gpu.OutputByte; - AcceleratorStream stream = AcceleratorStreamPool.Rent(accelerator); + AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); inputBuffer.View.CopyFromCPU(stream, values); kernel.Launch(stream, length, inputBuffer.View, resultBuffer.View); @@ -294,7 +298,7 @@ public sealed class PrimeTester resultBuffer.View.CopyToCPU(stream, in results); stream.Synchronize(); - AcceleratorStreamPool.Return(stream); + AcceleratorStreamPool.Return(acceleratorIndex, stream); PrimeOrderCalculatorAccelerator.Return(gpu); }