From 6b1c2d0665ae1bcdaeb28f201d7aae1974e86714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 25 Oct 2025 03:45:46 +0200 Subject: [PATCH 1/6] Reuse GPU by-divisor buffers to curb host allocations --- .../Gpu/MersenneNumberDivisorByDivisorGpuTester.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 1a2e728..bba681c 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -113,6 +113,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv resources.HitIndexBuffer, resources.Divisors, resources.Exponents, + resources.FilteredDivisors, resources.DivisorData, resources.Offsets, resources.Counts, @@ -177,8 +178,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv MemoryBuffer1D exponentBuffer, MemoryBuffer1D hitsBuffer, MemoryBuffer1D hitIndexBuffer, - in ulong[] divisors, + ulong[] divisors, ulong[] exponents, + ulong[] filteredDivisors, GpuDivisorPartialData[] divisorData, int[] offsets, int[] counts, @@ -229,8 +231,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv byte remainder11 = (byte)((ulong)(currentDivisor128 % 11UL)); bool lastIsSeven = (prime & 3UL) == 3UL; - ArrayPool ulongPool = ThreadStaticPools.UlongPool; - ulong[] filteredDivisors = ulongPool.Rent(chunkCapacity); Span filteredStorage = filteredDivisors.AsSpan(); Span divisorStorage = divisors.AsSpan(); Span exponentStorage = exponents.AsSpan(); @@ -373,8 +373,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex]; } - ulongPool.Return(filteredDivisors, clearArray: false); - coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128); return composite; } @@ -725,6 +723,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv Divisors = ulongPool.Rent(actualCapacity); Exponents = ulongPool.Rent(actualCapacity); + FilteredDivisors = ulongPool.Rent(actualCapacity); DivisorData = partialPool.Rent(actualCapacity); Offsets = intPool.Rent(actualCapacity); Counts = intPool.Rent(actualCapacity); @@ -753,6 +752,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal readonly ulong[] Exponents; + internal readonly ulong[] FilteredDivisors; + internal readonly GpuDivisorPartialData[] DivisorData; internal readonly int[] Offsets; @@ -780,6 +781,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ArrayPool partialPool = ThreadStaticPools.GpuDivisorPartialDataPool; ulongPool.Return(Divisors, clearArray: false); ulongPool.Return(Exponents, clearArray: false); + ulongPool.Return(FilteredDivisors, clearArray: false); partialPool.Return(DivisorData, clearArray: false); intPool.Return(Offsets, clearArray: false); intPool.Return(Counts, clearArray: false); From 3735a1cf0f052835f929a1ed6dfa8d4d33da559c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 25 Oct 2025 05:00:54 +0200 Subject: [PATCH 2/6] Gate GPU by-divisor scans to a single accelerator --- ...MersenneNumberDivisorByDivisorGpuTester.cs | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index bba681c..a80aee7 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -26,6 +26,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private readonly ConcurrentDictionary, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>> _kernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary> _resourcePools = new(AcceleratorReferenceComparer.Instance); 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) => @@ -679,15 +684,58 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private GpuContextPool.GpuContextLease RentAccelerator() { - if (_acceleratorPool.TryTake(out var lease)) + while (true) { - return lease; - } + if (_acceleratorPool.TryTake(out var lease)) + { + return lease; + } - return GpuContextPool.RentPreferred(preferCpu: false); + if (TryCreateAccelerator(out lease)) + { + return lease; + } + + Interlocked.Increment(ref _waitingAcceleratorCount); + try + { + _acceleratorAvailable.Wait(); + } + finally + { + Interlocked.Decrement(ref _waitingAcceleratorCount); + } + } } - private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease); + 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 BatchResources RentBatchResources(Accelerator accelerator, int capacity) { From 32f9f25e9ba9925bc124d2f7718d4e653c67e7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 25 Oct 2025 05:03:16 +0200 Subject: [PATCH 3/6] Share GPU by-divisor batch resources across accelerators --- ...MersenneNumberDivisorByDivisorGpuTester.cs | 293 ++++++++++-------- 1 file changed, 170 insertions(+), 123 deletions(-) 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(); From 2d103a96627ecc52bf8237f662f9084dc5e98dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 25 Oct 2025 05:41:05 +0200 Subject: [PATCH 4/6] Compute initial divisor remainders inside GPU kernel --- .../Gpu/Kernels/DivisorByDivisorKernels.cs | 4 +- ...MersenneNumberDivisorByDivisorGpuTester.cs | 43 +++---------------- 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs index accaa5f..327d25b 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs @@ -14,7 +14,6 @@ internal static class DivisorByDivisorKernels ArrayView exponentCounts, ArrayView exponents, ArrayView divisorCycles, - ArrayView firstCycleRemainders, ArrayView hits, ArrayView firstHit) { @@ -28,7 +27,6 @@ internal static class DivisorByDivisorKernels int offset = exponentOffsets[globalIndex]; GpuDivisorPartialData divisor = divisors[globalIndex]; ulong cycleLength = divisorCycles[globalIndex]; - ulong firstCycleRemainder = firstCycleRemainders[globalIndex]; var stepper = new ExponentRemainderStepperGpu(divisor); @@ -44,7 +42,7 @@ internal static class DivisorByDivisorKernels } else { - cycleRemainder = firstCycleRemainder; + cycleRemainder = firstExponent % cycleLength; firstHitValue = cycleRemainder == 0UL && firstUnity ? (byte)1 : (byte)0; } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index b38f45b..f363126 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -23,14 +23,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private ulong _divisorLimit; // private bool _isConfigured; - private readonly ConcurrentDictionary, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>> _kernelCache = new(AcceleratorReferenceComparer.Instance); + private readonly ConcurrentDictionary, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>> _kernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentBag _resourcePool = new(); private readonly ConcurrentBag _acceleratorPool = new(); private readonly ConcurrentBag _sessionPool = new(); - private Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> GetKernel(Accelerator accelerator) => + private Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> GetKernel(Accelerator accelerator) => _kernelCache.GetOrAdd(accelerator, acc => - acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(DivisorByDivisorKernels.CheckKernel)); + acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(DivisorByDivisorKernels.CheckKernel)); public int GpuBatchSize @@ -107,7 +107,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv resources.OffsetBuffer, resources.CountBuffer, resources.CycleBuffer, - resources.FirstRemainderBuffer, resources.ExponentBuffer, resources.HitsBuffer, resources.HitIndexBuffer, @@ -118,7 +117,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv resources.Offsets, resources.Counts, resources.Cycles, - resources.FirstRemainders, out lastProcessed, out coveredRange, out processedCount); @@ -169,12 +167,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private static bool CheckDivisors( ulong prime, ulong allowedMax, - Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> kernel, + Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> kernel, MemoryBuffer1D divisorDataBuffer, MemoryBuffer1D offsetBuffer, MemoryBuffer1D countBuffer, MemoryBuffer1D cycleBuffer, - MemoryBuffer1D firstRemainderBuffer, MemoryBuffer1D exponentBuffer, MemoryBuffer1D hitsBuffer, MemoryBuffer1D hitIndexBuffer, @@ -185,7 +182,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv int[] offsets, int[] counts, ulong[] cycles, - ulong[] firstRemainders, out ulong lastProcessed, out bool coveredRange, out ulong processedCount) @@ -238,8 +234,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv Span offsetStorage = offsets.AsSpan(); Span countStorage = counts.AsSpan(); Span cycleStorage = cycles.AsSpan(); - Span remainderStorage = firstRemainders.AsSpan(); - while (currentK <= maxK && !composite) { int chunkCount = Math.Min(chunkCapacity, batchCapacity); @@ -312,8 +306,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv Span offsetSpan = offsetStorage; Span countSpan = countStorage; Span cycleSpan = cycleStorage; - Span remainderSpan = remainderStorage; - int admissibleCount = 0; int exponentIndex = 0; for (int i = 0; i < filteredCount; i++) @@ -332,7 +324,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv offsetSpan[admissibleCount] = exponentIndex; countSpan[admissibleCount] = 1; cycleSpan[admissibleCount] = divisorCycle; - remainderSpan[admissibleCount] = 0UL; admissibleCount++; exponentIndex++; @@ -347,7 +338,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ArrayView1D offsetView = offsetBuffer.View.SubView(0, admissibleCount); ArrayView1D countView = countBuffer.View.SubView(0, admissibleCount); ArrayView1D cycleView = cycleBuffer.View.SubView(0, admissibleCount); - ArrayView1D remainderView = firstRemainderBuffer.View.SubView(0, admissibleCount); ArrayView1D exponentView = exponentBuffer.View.SubView(0, exponentIndex); ArrayView1D hitsView = hitsBuffer.View.SubView(0, exponentIndex); ArrayView1D hitIndexView = hitIndexBuffer.View.SubView(0, 1); @@ -356,13 +346,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv offsetView.CopyFromCPU(ref MemoryMarshal.GetReference(offsetSpan), admissibleCount); countView.CopyFromCPU(ref MemoryMarshal.GetReference(countSpan), admissibleCount); cycleView.CopyFromCPU(ref MemoryMarshal.GetReference(cycleSpan), admissibleCount); - remainderView.CopyFromCPU(ref MemoryMarshal.GetReference(remainderSpan), admissibleCount); exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), exponentIndex); int sentinel = int.MaxValue; hitIndexView.CopyFromCPU(ref sentinel, 1); - kernel(admissibleCount, divisorView, offsetView, countView, exponentView, cycleView, remainderView, hitsView, hitIndexView); + kernel(admissibleCount, divisorView, offsetView, countView, exponentView, cycleView, hitsView, hitIndexView); int firstHit = sentinel; hitIndexView.CopyToCPU(ref firstHit, 1); @@ -478,12 +467,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private readonly MersenneNumberDivisorByDivisorGpuTester _owner; private readonly GpuContextPool.GpuContextLease _lease; private readonly Accelerator _accelerator; - private Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> _kernel = null!; + 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 _firstRemainderBuffer = null!; private MemoryBuffer1D _firstHitBuffer = null!; private MemoryBuffer1D _exponentsBuffer = null!; private MemoryBuffer1D _hitBuffer = null!; @@ -517,7 +505,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv _offsetBuffer = _accelerator.Allocate1D(1); _countBuffer = _accelerator.Allocate1D(1); _cycleBuffer = _accelerator.Allocate1D(1); - _firstRemainderBuffer = _accelerator.Allocate1D(1); _firstHitBuffer = _accelerator.Allocate1D(1); } @@ -589,8 +576,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv } } - ulong firstRemainder = cycle == 0UL ? 0UL : firstPrime % cycle; - Monitor.Enter(_lease.ExecutionLock); EnsureExecutionResourcesLocked(length); @@ -599,7 +584,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ArrayView1D offsetView = _offsetBuffer.View; ArrayView1D countView = _countBuffer.View; ArrayView1D cycleView = _cycleBuffer.View; - ArrayView1D remainderView = _firstRemainderBuffer.View; ArrayView1D firstHitView = _firstHitBuffer.View; ArrayView1D exponentView = _exponentsBuffer.View; ArrayView1D hitView = _hitBuffer.View; @@ -616,9 +600,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ulong cycleValue = cycle; cycleView.SubView(0, 1).CopyFromCPU(ref cycleValue, 1); - ulong remainderValue = firstRemainder; - remainderView.SubView(0, 1).CopyFromCPU(ref remainderValue, 1); - Span hostSpan = _hostBuffer.AsSpan(0, length); primes.CopyTo(hostSpan); @@ -637,7 +618,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv throw new InvalidOperationException("GPU divisor kernel was not initialized."); } - kernel(1, divisorView, offsetView, countView, exponentSlice, cycleView, remainderView, hitSliceView, firstHitView); + kernel(1, divisorView, offsetView, countView, exponentSlice, cycleView, hitSliceView, firstHitView); Span hitSlice = hits.Slice(0, length); hitSliceView.CopyToCPU(ref MemoryMarshal.GetReference(hitSlice), length); @@ -709,7 +690,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private MemoryBuffer1D? _offsetBuffer; private MemoryBuffer1D? _countBuffer; private MemoryBuffer1D? _cycleBuffer; - private MemoryBuffer1D? _firstRemainderBuffer; private MemoryBuffer1D? _exponentBuffer; private MemoryBuffer1D? _hitsBuffer; private MemoryBuffer1D? _hitIndexBuffer; @@ -730,8 +710,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv 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."); @@ -752,7 +730,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal ulong[] Cycles { get; private set; } = null!; - internal ulong[] FirstRemainders { get; private set; } = null!; internal int Capacity => _hostCapacity; @@ -805,7 +782,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv _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); @@ -825,7 +801,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv Offsets = intPool.Rent(capacity); Counts = intPool.Rent(capacity); Cycles = ulongPool.Rent(capacity); - FirstRemainders = ulongPool.Rent(capacity); _hostCapacity = capacity; } @@ -835,7 +810,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv _offsetBuffer?.Dispose(); _countBuffer?.Dispose(); _cycleBuffer?.Dispose(); - _firstRemainderBuffer?.Dispose(); _exponentBuffer?.Dispose(); _hitsBuffer?.Dispose(); _hitIndexBuffer?.Dispose(); @@ -843,7 +817,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv _offsetBuffer = null; _countBuffer = null; _cycleBuffer = null; - _firstRemainderBuffer = null; _exponentBuffer = null; _hitsBuffer = null; _hitIndexBuffer = null; @@ -867,7 +840,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv intPool.Return(Offsets, clearArray: false); intPool.Return(Counts, clearArray: false); ulongPool.Return(Cycles, clearArray: false); - ulongPool.Return(FirstRemainders, clearArray: false); Divisors = Array.Empty(); Exponents = Array.Empty(); FilteredDivisors = Array.Empty(); @@ -875,7 +847,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv Offsets = Array.Empty(); Counts = Array.Empty(); Cycles = Array.Empty(); - FirstRemainders = Array.Empty(); _hostCapacity = 0; } From 8e5a1cd4c750f5012fd612c7e04b5146b5511f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 25 Oct 2025 05:58:50 +0200 Subject: [PATCH 5/6] Batch by-divisor GPU tasks across prime ranges --- EvenPerfectBitScanner/Program.cs | 4 +++- ...rsenneNumberDivisorByDivisorTesterTests.cs | 3 ++- .../MersenneNumberDivisorByDivisorTester.cs | 21 +++++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 6cc12b4..767bac8 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -419,6 +419,7 @@ internal static class Program if (useByDivisor) { + int byDivisorPrimeRange = Math.Max(1, _cliArguments.BlockSize); MersenneNumberDivisorByDivisorTester.Run( byDivisorCandidates, _byDivisorTester!, @@ -427,7 +428,8 @@ internal static class Program static () => _lastCompositeP = true, static () => _lastCompositeP = false, static (candidate, searchedMersenne, detailedCheck, passedAllTests) => CalculationResultHandler.HandleResult(candidate, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP), - threadCount); + threadCount, + byDivisorPrimeRange); CalculationResultHandler.FlushBuffer(); CalculationResultHandler.ReleaseOutputBuffer(); diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs index 49778cc..35b2fda 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs @@ -23,7 +23,8 @@ public class MersenneNumberDivisorByDivisorTesterTests markComposite: () => compositeMarks++, clearComposite: () => clearedMarks++, printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)), - threadCount: 1); + threadCount: 1, + primesPerTask: 2); tester.ConfiguredMaxPrime.Should().Be(13UL); compositeMarks.Should().Be(2); diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index 008ddd5..dbbb8d9 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Collections.Concurrent; using System.Globalization; using System.Runtime.InteropServices; @@ -14,7 +15,8 @@ public static class MersenneNumberDivisorByDivisorTester Action markComposite, Action clearComposite, Action printResult, - int threadCount) + int threadCount, + int primesPerTask) { if (candidates.Count == 0) { @@ -233,6 +235,8 @@ public static class MersenneNumberDivisorByDivisorTester // workerCount = 1; // } + int chunkSize = primesPerTask <= 0 ? 1 : primesPerTask; + void ProcessPrime(ulong prime) { bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted); @@ -264,7 +268,20 @@ public static class MersenneNumberDivisorByDivisorTester TaskScheduler = scheduler }; - Parallel.ForEach(filteredPrimes, options, ProcessPrime); + int totalCount = filteredPrimes.Count; + int partitionSize = chunkSize < 1 ? 1 : chunkSize; + if (partitionSize > totalCount) + { + partitionSize = totalCount; + } + + Parallel.ForEach(Partitioner.Create(0, totalCount, partitionSize), options, range => + { + for (int index = range.Item1; index < range.Item2; index++) + { + ProcessPrime(filteredPrimes[index]); + } + }); } } } From 74ab4e91e65a879b04047c9b4a23b39dfd09b3b9 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 25 Oct 2025 05:59:47 +0200 Subject: [PATCH 6/6] Console logging info added to know when a task starts & completes --- PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index 008ddd5..9a576ec 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -235,6 +235,7 @@ public static class MersenneNumberDivisorByDivisorTester void ProcessPrime(ulong prime) { + Console.WriteLine($"Task started {prime}"); bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted); if (!isPrime) @@ -246,6 +247,7 @@ public static class MersenneNumberDivisorByDivisorTester clearComposite(); printResult(prime, true, divisorsExhausted, true); + Console.WriteLine($"Task finished {prime}"); } if (workerCount == 1)