From 3fd39c3d3446d3bef610494ea63229b9c3229c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 21 Sep 2025 11:41:51 +0200 Subject: [PATCH] Wire by-divisor GPU batching to scanner configuration --- EvenPerfectBitScanner/Program.cs | 5 +- ...MersenneNumberDivisorByDivisorGpuTester.cs | 238 +++++++++++------- 2 files changed, 157 insertions(+), 86 deletions(-) diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 85f5bab..a4b1cde 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -495,7 +495,10 @@ internal static class Program } else if (useByDivisor) { - _byDivisorTester = new MersenneNumberDivisorByDivisorGpuTester(); + _byDivisorTester = new MersenneNumberDivisorByDivisorGpuTester + { + GpuBatchSize = scanBatchSize, + }; } // Load RLE blacklist (optional) diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 6133440..76f153c 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -11,6 +11,8 @@ namespace PerfectNumbers.Core.Gpu; public sealed class MersenneNumberDivisorByDivisorGpuTester { + private int _gpuBatchSize = GpuConstants.ScanBatchSize; + private readonly List _divisors = new(); private readonly List _remainders = new(); private readonly List _lastPrimes = new(); @@ -28,6 +30,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester private Action, ArrayView, ArrayView> GetInitialKernel(Accelerator accelerator) => _initialKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView>(InitialKernel)); + public int GpuBatchSize + { + get => _gpuBatchSize; + set => _gpuBatchSize = Math.Max(1, value); + } + public void ConfigureFromMaxPrime(ulong maxPrime) { lock (_sync) @@ -80,138 +88,198 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester return false; } + var divisorsSpan = CollectionsMarshal.AsSpan(_divisors); + var remaindersSpan = CollectionsMarshal.AsSpan(_remainders); + var lastPrimesSpan = CollectionsMarshal.AsSpan(_lastPrimes); + var gpuLease = GpuContextPool.RentPreferred(preferCpu: false); var accelerator = gpuLease.Accelerator; var kernel = GetUpdateKernel(accelerator); - var divisorsBuffer = accelerator.Allocate1D(count); - var remaindersBuffer = accelerator.Allocate1D(count); - var lastPrimesBuffer = accelerator.Allocate1D(count); - var hitsBuffer = accelerator.Allocate1D(count); - Span divisorsSpan = CollectionsMarshal.AsSpan(_divisors); - Span remaindersSpan = CollectionsMarshal.AsSpan(_remainders); - Span lastPrimesSpan = CollectionsMarshal.AsSpan(_lastPrimes); + int batchCapacity = Math.Min(_gpuBatchSize, count); + var divisorsBuffer = accelerator.Allocate1D(batchCapacity); + var remaindersBuffer = accelerator.Allocate1D(batchCapacity); + var lastPrimesBuffer = accelerator.Allocate1D(batchCapacity); + var hitsBuffer = accelerator.Allocate1D(batchCapacity); - divisorsBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(divisorsSpan), count); - remaindersBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(remaindersSpan), count); - lastPrimesBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(lastPrimesSpan), count); - hitsBuffer.MemSetToZero(); - - kernel(count, prime, divisorsBuffer.View, remaindersBuffer.View, lastPrimesBuffer.View, hitsBuffer.View); - accelerator.Synchronize(); - - remaindersBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(remaindersSpan), count); - lastPrimesBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(lastPrimesSpan), count); - - byte[] hits = ArrayPool.Shared.Rent(count); - Span hitsSpan = hits.AsSpan(0, count); - hitsBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), count); + byte[] hits = ArrayPool.Shared.Rent(batchCapacity); bool composite = false; ulong lastProcessed = 0UL; - for (int i = 0; i < count; i++) - { - ulong divisor = divisorsSpan[i]; - if (divisor > allowedMax) - { - break; - } - lastProcessed = divisor; - if (hitsSpan[i] != 0) + try + { + int processed = 0; + while (processed < count) { - composite = true; - break; + int batchSize = Math.Min(batchCapacity, count - processed); + var divisorView = divisorsBuffer.View.SubView(0, batchSize); + var remainderView = remaindersBuffer.View.SubView(0, batchSize); + var lastPrimeView = lastPrimesBuffer.View.SubView(0, batchSize); + var hitsView = hitsBuffer.View.SubView(0, batchSize); + + Span divisorBatch = divisorsSpan.Slice(processed, batchSize); + Span remainderBatch = remaindersSpan.Slice(processed, batchSize); + Span lastPrimeBatch = lastPrimesSpan.Slice(processed, batchSize); + + divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorBatch), batchSize); + remainderView.CopyFromCPU(ref MemoryMarshal.GetReference(remainderBatch), batchSize); + lastPrimeView.CopyFromCPU(ref MemoryMarshal.GetReference(lastPrimeBatch), batchSize); + hitsView.MemSetToZero(); + + kernel(batchSize, prime, divisorView, remainderView, lastPrimeView, hitsView); + accelerator.Synchronize(); + + remainderView.CopyToCPU(ref MemoryMarshal.GetReference(remainderBatch), batchSize); + lastPrimeView.CopyToCPU(ref MemoryMarshal.GetReference(lastPrimeBatch), batchSize); + + Span hitsSpan = hits.AsSpan(0, batchSize); + hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize); + + for (int i = 0; i < batchSize; i++) + { + ulong divisor = divisorBatch[i]; + if (divisor > allowedMax) + { + processed = count; + break; + } + + lastProcessed = divisor; + if (hitsSpan[i] != 0) + { + composite = true; + processed = count; + break; + } + } + + if (composite) + { + break; + } + + if (processed < count) + { + processed += batchSize; + } } } + finally + { + ArrayPool.Shared.Return(hits, clearArray: true); + hitsBuffer.Dispose(); + lastPrimesBuffer.Dispose(); + remaindersBuffer.Dispose(); + divisorsBuffer.Dispose(); + gpuLease.Dispose(); + } if (lastProcessed != 0UL) { ReportStatus(lastProcessed); } - ArrayPool.Shared.Return(hits, clearArray: true); - - hitsBuffer.Dispose(); - lastPrimesBuffer.Dispose(); - remaindersBuffer.Dispose(); - divisorsBuffer.Dispose(); - gpuLease.Dispose(); - return composite; } private bool ExtendDivisors(ulong prime, ulong allowedMax) { - ulong currentMax = _divisors.Count == 0 ? 1UL : _divisors[^1]; + int existingCount = _divisors.Count; + allowedMax = Math.Min(allowedMax, _divisorLimit); + + ulong currentMax = existingCount == 0 ? 1UL : _divisors[^1]; ulong start = currentMax < 3UL ? 3UL : currentMax + 2UL; if (start > allowedMax) { return false; } - List newDivisors = new(); - for (ulong divisor = start; divisor <= allowedMax; divisor += 2UL) - { - newDivisors.Add(divisor); - } - - int count = newDivisors.Count; - if (count == 0) - { - return false; - } - var gpuLease = GpuContextPool.RentPreferred(preferCpu: false); var accelerator = gpuLease.Accelerator; var kernel = GetInitialKernel(accelerator); - var divisorsBuffer = accelerator.Allocate1D(count); - var remaindersBuffer = accelerator.Allocate1D(count); - var hitsBuffer = accelerator.Allocate1D(count); - Span newDivisorsSpan = CollectionsMarshal.AsSpan(newDivisors); - divisorsBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(newDivisorsSpan), count); - hitsBuffer.MemSetToZero(); + int batchCapacity = _gpuBatchSize; + var divisorsBuffer = accelerator.Allocate1D(batchCapacity); + var remaindersBuffer = accelerator.Allocate1D(batchCapacity); + var hitsBuffer = accelerator.Allocate1D(batchCapacity); - kernel(count, prime, divisorsBuffer.View, remaindersBuffer.View, hitsBuffer.View); - accelerator.Synchronize(); - - ulong[] newRemainders = ArrayPool.Shared.Rent(count); - byte[] hits = ArrayPool.Shared.Rent(count); - Span newRemaindersSpan = newRemainders.AsSpan(0, count); - Span hitsSpan = hits.AsSpan(0, count); - remaindersBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(newRemaindersSpan), count); - hitsBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), count); + ulong[] remainders = ArrayPool.Shared.Rent(batchCapacity); + byte[] hits = ArrayPool.Shared.Rent(batchCapacity); + ulong[] divisors = ArrayPool.Shared.Rent(batchCapacity); bool composite = false; ulong lastProcessed = 0UL; - for (int i = 0; i < count; i++) + ulong divisor = start; + + try { - ulong divisor = newDivisorsSpan[i]; - ulong remainder = newRemaindersSpan[i]; - _divisors.Add(divisor); - _remainders.Add(remainder); - _lastPrimes.Add(prime); - lastProcessed = divisor; - if (!composite && hitsSpan[i] != 0) + while (divisor <= allowedMax) { - composite = true; + int batchSize = batchCapacity; + int index = 0; + while (index < batchSize && divisor <= allowedMax) + { + divisors[index++] = divisor; + divisor += 2UL; + } + + if (index == 0) + { + break; + } + + var divisorView = divisorsBuffer.View.SubView(0, index); + var remainderView = remaindersBuffer.View.SubView(0, index); + var hitsView = hitsBuffer.View.SubView(0, index); + + Span divisorSpan = divisors.AsSpan(0, index); + divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorSpan), index); + hitsView.MemSetToZero(); + + kernel(index, prime, divisorView, remainderView, hitsView); + accelerator.Synchronize(); + + Span remainderSpan = remainders.AsSpan(0, index); + Span hitsSpan = hits.AsSpan(0, index); + remainderView.CopyToCPU(ref MemoryMarshal.GetReference(remainderSpan), index); + hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), index); + + for (int i = 0; i < index; i++) + { + ulong newDivisor = divisorSpan[i]; + _divisors.Add(newDivisor); + _remainders.Add(remainderSpan[i]); + _lastPrimes.Add(prime); + lastProcessed = newDivisor; + if (!composite && hitsSpan[i] != 0) + { + composite = true; + } + } + + if (composite) + { + break; + } } } + finally + { + ArrayPool.Shared.Return(divisors, clearArray: true); + ArrayPool.Shared.Return(remainders, clearArray: true); + ArrayPool.Shared.Return(hits, clearArray: true); + hitsBuffer.Dispose(); + remaindersBuffer.Dispose(); + divisorsBuffer.Dispose(); + gpuLease.Dispose(); + } if (lastProcessed != 0UL) { ReportStatus(lastProcessed); } - ArrayPool.Shared.Return(newRemainders, clearArray: true); - ArrayPool.Shared.Return(hits, clearArray: true); - - hitsBuffer.Dispose(); - remaindersBuffer.Dispose(); - divisorsBuffer.Dispose(); - gpuLease.Dispose(); - return composite; }