diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index a80aee7..b38f45b 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -24,13 +24,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv // private bool _isConfigured; private readonly ConcurrentDictionary, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>> _kernelCache = new(AcceleratorReferenceComparer.Instance); - private readonly ConcurrentDictionary> _resourcePools = new(AcceleratorReferenceComparer.Instance); + private readonly ConcurrentBag _resourcePool = new(); private readonly ConcurrentBag _acceleratorPool = new(); - private readonly SemaphoreSlim _acceleratorAvailable = new(0); - private int _createdAccelerators; - // Limit GPU context concurrency to avoid replicating the large batch buffers across thousands of worker threads. - private int _acceleratorLimit = 1; - private int _waitingAcceleratorCount; private readonly ConcurrentBag _sessionPool = new(); private Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> GetKernel(Accelerator accelerator) => @@ -128,7 +123,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv out coveredRange, out processedCount); - ReturnBatchResources(accelerator, resources); + ReturnBatchResources(resources); Monitor.Exit(gpuLease.ExecutionLock); ReturnAccelerator(gpuLease); @@ -684,146 +679,184 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private GpuContextPool.GpuContextLease RentAccelerator() { - while (true) + if (_acceleratorPool.TryTake(out var lease)) { - if (_acceleratorPool.TryTake(out var lease)) - { - return lease; - } - - if (TryCreateAccelerator(out lease)) - { - return lease; - } - - Interlocked.Increment(ref _waitingAcceleratorCount); - try - { - _acceleratorAvailable.Wait(); - } - finally - { - Interlocked.Decrement(ref _waitingAcceleratorCount); - } + return lease; } + + return GpuContextPool.RentPreferred(preferCpu: false); } - private bool TryCreateAccelerator(out GpuContextPool.GpuContextLease lease) - { - while (true) - { - int created = Volatile.Read(ref _createdAccelerators); - int limit = Volatile.Read(ref _acceleratorLimit); - if (created >= limit) - { - lease = default; - return false; - } - - if (Interlocked.CompareExchange(ref _createdAccelerators, created + 1, created) == created) - { - lease = GpuContextPool.RentPreferred(preferCpu: false); - return true; - } - } - } - - private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) - { - _acceleratorPool.Add(lease); - if (Volatile.Read(ref _waitingAcceleratorCount) > 0) - { - _acceleratorAvailable.Release(); - } - } + private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease); private BatchResources RentBatchResources(Accelerator accelerator, int capacity) { - var bag = _resourcePools.GetOrAdd(accelerator, static _ => []); - if (bag.TryTake(out BatchResources? resources)) + if (!_resourcePool.TryTake(out BatchResources? resources)) { - return resources; + resources = new BatchResources(Math.Max(1, capacity)); } - return new BatchResources(accelerator, capacity); + resources.Bind(accelerator, capacity); + return resources; } - private void ReturnBatchResources(Accelerator accelerator, BatchResources resources) => _resourcePools.GetOrAdd(accelerator, static _ => []).Add(resources); + private void ReturnBatchResources(BatchResources resources) => _resourcePool.Add(resources); private sealed class BatchResources : IDisposable { - internal BatchResources(Accelerator accelerator, int capacity) + private Accelerator? _accelerator; + private MemoryBuffer1D? _divisorDataBuffer; + private MemoryBuffer1D? _offsetBuffer; + private MemoryBuffer1D? _countBuffer; + private MemoryBuffer1D? _cycleBuffer; + private MemoryBuffer1D? _firstRemainderBuffer; + 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); + } - DivisorDataBuffer = accelerator.Allocate1D(actualCapacity); - OffsetBuffer = accelerator.Allocate1D(actualCapacity); - CountBuffer = accelerator.Allocate1D(actualCapacity); - CycleBuffer = accelerator.Allocate1D(actualCapacity); - FirstRemainderBuffer = accelerator.Allocate1D(actualCapacity); - ExponentBuffer = accelerator.Allocate1D(actualCapacity); - HitsBuffer = accelerator.Allocate1D(actualCapacity); - HitIndexBuffer = accelerator.Allocate1D(1); + 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 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 FirstRemainderBuffer => _firstRemainderBuffer ?? 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 HitIndexBuffer => _hitIndexBuffer ?? throw new InvalidOperationException("Batch resources not bound to an accelerator."); + + internal ulong[] Divisors { get; private set; } = null!; + + internal ulong[] Exponents { get; private set; } = null!; + + internal ulong[] FilteredDivisors { get; private set; } = null!; + + internal GpuDivisorPartialData[] DivisorData { get; private set; } = null!; + + internal int[] Offsets { get; private set; } = null!; + + internal int[] Counts { get; private set; } = null!; + + internal ulong[] Cycles { get; private set; } = null!; + + internal ulong[] FirstRemainders { get; private set; } = null!; + + internal int Capacity => _hostCapacity; + + internal void Bind(Accelerator accelerator, int requiredCapacity) + { + EnsureHostCapacity(requiredCapacity); + + if (!ReferenceEquals(_accelerator, accelerator)) + { + ReleaseDeviceBuffers(); + _accelerator = accelerator; + } + + EnsureDeviceCapacity(accelerator, requiredCapacity); + } + + private void EnsureHostCapacity(int requiredCapacity) + { + int desiredCapacity = Math.Max(1, requiredCapacity); + if (_hostCapacity >= desiredCapacity) + { + return; + } + + ReturnHostArrays(); + RentHostArrays(desiredCapacity); + } + + 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; + } + + ReleaseDeviceBuffers(); + AllocateDeviceBuffers(accelerator, desiredCapacity); + } + + private void AllocateDeviceBuffers(Accelerator accelerator, int capacity) + { + _divisorDataBuffer = accelerator.Allocate1D(capacity); + _offsetBuffer = accelerator.Allocate1D(capacity); + _countBuffer = accelerator.Allocate1D(capacity); + _cycleBuffer = accelerator.Allocate1D(capacity); + _firstRemainderBuffer = accelerator.Allocate1D(capacity); + _exponentBuffer = accelerator.Allocate1D(capacity); + _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; - Divisors = ulongPool.Rent(actualCapacity); - Exponents = ulongPool.Rent(actualCapacity); - FilteredDivisors = ulongPool.Rent(actualCapacity); - DivisorData = partialPool.Rent(actualCapacity); - Offsets = intPool.Rent(actualCapacity); - Counts = intPool.Rent(actualCapacity); - Cycles = ulongPool.Rent(actualCapacity); - FirstRemainders = ulongPool.Rent(actualCapacity); - Capacity = actualCapacity; + Divisors = ulongPool.Rent(capacity); + Exponents = ulongPool.Rent(capacity); + FilteredDivisors = ulongPool.Rent(capacity); + DivisorData = partialPool.Rent(capacity); + Offsets = intPool.Rent(capacity); + Counts = intPool.Rent(capacity); + Cycles = ulongPool.Rent(capacity); + FirstRemainders = ulongPool.Rent(capacity); + _hostCapacity = capacity; } - internal readonly MemoryBuffer1D DivisorDataBuffer; - - internal readonly MemoryBuffer1D OffsetBuffer; - - internal readonly MemoryBuffer1D CountBuffer; - - internal readonly MemoryBuffer1D CycleBuffer; - - internal readonly MemoryBuffer1D FirstRemainderBuffer; - - internal readonly MemoryBuffer1D ExponentBuffer; - - internal readonly MemoryBuffer1D HitsBuffer; - - internal readonly MemoryBuffer1D HitIndexBuffer; - - internal readonly ulong[] Divisors; - - internal readonly ulong[] Exponents; - - internal readonly ulong[] FilteredDivisors; - - internal readonly GpuDivisorPartialData[] DivisorData; - - internal readonly int[] Offsets; - - internal readonly int[] Counts; - - internal readonly ulong[] Cycles; - - internal readonly ulong[] FirstRemainders; - - internal readonly int Capacity; - - public void Dispose() + private void ReleaseDeviceBuffers() { - DivisorDataBuffer.Dispose(); - OffsetBuffer.Dispose(); - CountBuffer.Dispose(); - CycleBuffer.Dispose(); - FirstRemainderBuffer.Dispose(); - ExponentBuffer.Dispose(); - HitsBuffer.Dispose(); - HitIndexBuffer.Dispose(); + _divisorDataBuffer?.Dispose(); + _offsetBuffer?.Dispose(); + _countBuffer?.Dispose(); + _cycleBuffer?.Dispose(); + _firstRemainderBuffer?.Dispose(); + _exponentBuffer?.Dispose(); + _hitsBuffer?.Dispose(); + _hitIndexBuffer?.Dispose(); + _divisorDataBuffer = null; + _offsetBuffer = null; + _countBuffer = null; + _cycleBuffer = null; + _firstRemainderBuffer = null; + _exponentBuffer = null; + _hitsBuffer = null; + _hitIndexBuffer = null; + _deviceCapacity = 0; + } + + private void ReturnHostArrays() + { + if (_hostCapacity == 0) + { + return; + } + ArrayPool ulongPool = ThreadStaticPools.UlongPool; ArrayPool intPool = ThreadStaticPools.IntPool; ArrayPool partialPool = ThreadStaticPools.GpuDivisorPartialDataPool; @@ -835,11 +868,25 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv intPool.Return(Counts, clearArray: false); ulongPool.Return(Cycles, clearArray: false); ulongPool.Return(FirstRemainders, clearArray: false); + Divisors = Array.Empty(); + Exponents = Array.Empty(); + FilteredDivisors = Array.Empty(); + DivisorData = Array.Empty(); + Offsets = Array.Empty(); + Counts = Array.Empty(); + Cycles = Array.Empty(); + FirstRemainders = Array.Empty(); + _hostCapacity = 0; + } + + public void Dispose() + { + ReleaseDeviceBuffers(); + _accelerator = null; + ReturnHostArrays(); } } - - private sealed class AcceleratorReferenceComparer : IEqualityComparer { public static AcceleratorReferenceComparer Instance { get; } = new();