diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index 4ee09a7..70cc274 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -18,8 +18,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv private ulong _configuredMaxPrime; private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu; - private MersenneNumberDivisorByDivisorGpuTester? _gpuDeltasTester; - private bool _gpuTesterConfigured; public int BatchSize @@ -29,14 +27,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { int sanitized = Math.Max(1, value); _batchSize = sanitized; - - lock (_sync) - { - if (_gpuDeltasTester is not null) - { - _gpuDeltasTester.GpuBatchSize = sanitized; - } - } } } @@ -60,7 +50,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); _lastStatusDivisor = 0UL; _isConfigured = true; - _gpuTesterConfigured = false; } } @@ -167,31 +156,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } } - internal IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateGpuDeltasSession() - { - MersenneNumberDivisorByDivisorGpuTester tester; - - lock (_sync) - { - if (!_isConfigured) - { - throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); - } - - tester = _gpuDeltasTester ??= new MersenneNumberDivisorByDivisorGpuTester(); - - if (!_gpuTesterConfigured) - { - tester.ConfigureFromMaxPrime(_configuredMaxPrime); - _gpuTesterConfigured = true; - } - - tester.GpuBatchSize = _batchSize; - } - - return tester.CreateDivisorSession(); - } - public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() { lock (_sync) @@ -514,7 +478,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv private ulong[]? _cycleRemainders; private ulong[]? _montgomeryDeltas; private int _bufferCapacity; - private IMersenneNumberDivisorByDivisorTester.IDivisorScanSession? _gpuSession; + private MersenneNumberDivisorResidueGpuEvaluator? _gpuResidueEvaluator; internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner) { @@ -572,13 +536,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } } - if (_owner._deltasDevice == ByDivisorDeltasDevice.Gpu) - { - EnsureGpuSession(); - _gpuSession!.CheckDivisor(divisor, cachedData, divisorCycle, primes, hits); - return; - } - EnsureCapacity(length); Span deltaSpan = _primeDeltas!.AsSpan(0, length); @@ -587,11 +544,27 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv Span remainderSpan = _cycleRemainders!.AsSpan(0, length); ComputeCycleRemainders(divisorCycle, primes, deltaSpan, remainderSpan); + ulong montgomeryOne = cachedData.MontgomeryOne; + + if (_owner._deltasDevice == ByDivisorDeltasDevice.Gpu) + { + EnsureGpuEvaluator(); + Span residueSpan = _montgomeryDeltas!.AsSpan(0, length); + // Reuse the Montgomery delta buffer to hold the final residues returned by the GPU evaluator. + _gpuResidueEvaluator!.ComputeResidues(primes, cachedData, residueSpan); + + for (int i = 0; i < length; i++) + { + hits[i] = remainderSpan[i] == 0UL && residueSpan[i] == montgomeryOne ? (byte)1 : (byte)0; + } + + return; + } + Span montgomerySpan = _montgomeryDeltas!.AsSpan(0, length); ComputeMontgomeryDeltas(cachedData, primes, deltaSpan, montgomerySpan); ulong nPrime = cachedData.NPrime; - ulong montgomeryOne = cachedData.MontgomeryOne; ulong currentMontgomery = montgomerySpan[0]; hits[0] = remainderSpan[0] == 0UL && currentMontgomery == montgomeryOne ? (byte)1 : (byte)0; @@ -603,23 +576,23 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } } - private void EnsureGpuSession() + private void EnsureGpuEvaluator() { - if (_gpuSession is null) + if (_gpuResidueEvaluator is null) { - _gpuSession = _owner.CreateGpuDeltasSession(); + _gpuResidueEvaluator = new MersenneNumberDivisorResidueGpuEvaluator(_owner._batchSize); } } - private void ReleaseGpuSession() + private void ReleaseGpuEvaluator() { - if (_gpuSession is null) + if (_gpuResidueEvaluator is null) { return; } - _gpuSession.Dispose(); - _gpuSession = null; + _gpuResidueEvaluator.Dispose(); + _gpuResidueEvaluator = null; } private void EnsureCapacity(int requiredLength) @@ -767,7 +740,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } _disposed = true; - ReleaseGpuSession(); + ReleaseGpuEvaluator(); _owner.ReturnSession(this); } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs new file mode 100644 index 0000000..8fdd163 --- /dev/null +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs @@ -0,0 +1,114 @@ +using System; +using System.Buffers; +using System.Runtime.InteropServices; +using PerfectNumbers.Core; +using ILGPU; +using ILGPU.Runtime; + +namespace PerfectNumbers.Core.Gpu; + +internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable +{ + private readonly GpuContextPool.GpuContextLease _lease; + private readonly Accelerator _accelerator; + private readonly Action, ArrayView> _kernel; + private MemoryBuffer1D _exponentBuffer; + private MemoryBuffer1D _resultBuffer; + private ulong[] _hostBuffer; + private int _capacity; + private bool _disposed; + + internal MersenneNumberDivisorResidueGpuEvaluator(int batchSize) + { + _lease = GpuContextPool.RentPreferred(preferCpu: false); + _accelerator = _lease.Accelerator; + _kernel = _accelerator.LoadAutoGroupedStreamKernel, ArrayView>(ComputeResiduesKernel); + _capacity = Math.Max(1, batchSize); + _exponentBuffer = _accelerator.Allocate1D(_capacity); + _resultBuffer = _accelerator.Allocate1D(_capacity); + _hostBuffer = ArrayPool.Shared.Rent(_capacity); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + _exponentBuffer.Dispose(); + _resultBuffer.Dispose(); + ArrayPool.Shared.Return(_hostBuffer, clearArray: false); + _lease.Dispose(); + } + + internal void ComputeResidues(ReadOnlySpan exponents, in MontgomeryDivisorData divisorData, Span destination) + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(MersenneNumberDivisorResidueGpuEvaluator)); + } + + int length = exponents.Length; + if (length == 0) + { + return; + } + + EnsureCapacity(length); + + Span hostSpan = _hostBuffer.AsSpan(0, length); + exponents.CopyTo(hostSpan); + + ArrayView1D exponentView = _exponentBuffer.View.SubView(0, length); + exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), length); + + ArrayView1D resultView = _resultBuffer.View.SubView(0, length); + _kernel(length, divisorData, exponentView, resultView); + resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostSpan), length); + + hostSpan.CopyTo(destination); + } + + private void EnsureCapacity(int required) + { + if (required <= _capacity) + { + return; + } + + int newCapacity = _capacity; + if (newCapacity == 0) + { + newCapacity = 1; + } + + while (newCapacity < required) + { + newCapacity <<= 1; + } + + _exponentBuffer.Dispose(); + _resultBuffer.Dispose(); + ArrayPool.Shared.Return(_hostBuffer, clearArray: false); + + _capacity = newCapacity; + _exponentBuffer = _accelerator.Allocate1D(_capacity); + _resultBuffer = _accelerator.Allocate1D(_capacity); + _hostBuffer = ArrayPool.Shared.Rent(_capacity); + } + + private static void ComputeResiduesKernel(Index1D index, MontgomeryDivisorData divisorData, ArrayView exponents, ArrayView results) + { + int idx = index; + int length = (int)exponents.Length; + if (idx >= length) + { + return; + } + + ulong exponent = exponents[idx]; + results[idx] = exponent.Pow2MontgomeryModWindowedGpu(divisorData, keepMontgomery: true); + } +}