From bae39ad95e338f25118025daa3757d76d5498a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 22 Oct 2025 06:43:05 +0200 Subject: [PATCH] Capture GPU next start for divisor batches --- .../Gpu/Kernels/DivisorByDivisorKernels.cs | 17 +++++ ...MersenneNumberDivisorByDivisorGpuTester.cs | 69 +++++++++++-------- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs index 35e4170..04f67f7 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs @@ -21,6 +21,7 @@ internal static class DivisorByDivisorKernels Index1D index, ArrayView candidates, ArrayView gaps, + ArrayView nextStarts, GpuUInt128 startValue, GpuUInt128 stride) { @@ -55,6 +56,22 @@ internal static class DivisorByDivisorKernels GpuUInt128 gapValue = candidateValue; gapValue.Sub(previousValue); gaps[globalIndex] = gapValue.Low; + + if (globalIndex == 0) + { + // EvenPerfectBitScanner provides a single-element nextStarts view per launch, so the guard stays commented out. + // int nextStartLength = (int)nextStarts.Length; + // if (nextStartLength <= 0) + // { + // return; + // } + + ulong candidateCount = (ulong)candidates.Length; + GpuUInt128 nextValue = stride; + nextValue.Mul(candidateCount); + nextValue.Add(startValue); + nextStarts[0] = nextValue; + } } public static void ComputeRemainderDeltasKernel(Index1D index, ArrayView gaps, ArrayView deltas, byte modulus) diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 5a988bf..70e1292 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -28,7 +28,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private readonly ConcurrentDictionary, ArrayView, byte>> _remainderDeltaKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary, ArrayView, byte, byte>> _remainderScanKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary, ArrayView, ArrayView, ArrayView, byte, ArrayView>> _candidateMaskKernelCache = new(AcceleratorReferenceComparer.Instance); - private readonly ConcurrentDictionary, ArrayView, GpuUInt128, GpuUInt128>> _candidateGenerationKernelCache = new(AcceleratorReferenceComparer.Instance); + private readonly ConcurrentDictionary, ArrayView, ArrayView, GpuUInt128, GpuUInt128>> _candidateGenerationKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary> _resourcePools = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentBag _acceleratorPool = new(); private readonly ConcurrentBag _sessionPool = new(); @@ -52,10 +52,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, byte, ArrayView>(DivisorByDivisorKernels.EvaluateCandidateMaskKernel)); - private Action, ArrayView, GpuUInt128, GpuUInt128> GetCandidateGenerationKernel(Accelerator accelerator) => + private Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> GetCandidateGenerationKernel(Accelerator accelerator) => _candidateGenerationKernelCache.GetOrAdd( accelerator, - acc => acc.LoadAutoGroupedStreamKernel, ArrayView, GpuUInt128, GpuUInt128>(DivisorByDivisorKernels.GenerateCandidatesAndGapsKernel)); + acc => acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, GpuUInt128, GpuUInt128>(DivisorByDivisorKernels.GenerateCandidatesAndGapsKernel)); public int GpuBatchSize { @@ -132,6 +132,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv resources.ExponentBuffer, resources.HitsBuffer, resources.CandidateBuffer, + resources.NextStartBuffer, resources.DivisorDeltaBuffer, resources.RemainderDeltaBuffer, resources.RemainderBuffer, @@ -202,13 +203,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv MemoryBuffer1D exponentBuffer, MemoryBuffer1D hitsBuffer, MemoryBuffer1D candidateBuffer, + MemoryBuffer1D nextStartBuffer, MemoryBuffer1D divisorDeltaBuffer, MemoryBuffer1D remainderDeltaBuffer, MemoryBuffer1D remainderBuffer, Action, ArrayView, byte> remainderDeltaKernel, Action, ArrayView, byte, byte> remainderScanKernel, Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> candidateMaskKernel, - Action, ArrayView, GpuUInt128, GpuUInt128> candidateGenerationKernel, + Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> candidateGenerationKernel, in ulong[] divisors, ulong[] exponents, byte[] hits, @@ -304,27 +306,27 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ArrayView1D maskView = viewCache.Hits; UInt128 localDivisor = currentDivisor128; - UInt128 nextDivisor; ArrayView1D candidateView = candidateBuffer.View.SubView(0, effectiveCount); + ArrayView1D nextStartView = nextStartBuffer.View.SubView(0, 1); GpuUInt128 gpuStartValue = new GpuUInt128(localDivisor); GpuUInt128 gpuStride = new GpuUInt128(twoP128); - GenerateCandidatesOnGpu(effectiveCount, gpuStartValue, gpuStride, candidateView, gapView, candidateGenerationKernel); + GpuUInt128 nextDivisorGpu = GenerateCandidatesOnGpu( + effectiveCount, + gpuStartValue, + gpuStride, + candidateView, + gapView, + nextStartView, + candidateGenerationKernel); if (effectiveCount > 0) { candidateView.CopyToCPU(ref MemoryMarshal.GetReference(candidateSpan), effectiveCount); - } - - nextDivisor = localDivisor + twoP128 * (UInt128)effectiveCount; - - processedCount += (ulong)effectiveCount; - if (effectiveCount > 0) - { + processedCount += (ulong)effectiveCount; lastProcessed = candidateSpan[effectiveCount - 1]; + currentDivisor128 = (UInt128)nextDivisorGpu; } - currentDivisor128 = nextDivisor; - ComputeRemaindersOnGpu(effectiveCount, remainder10, 10, gapView, deltaView, remainder10View, remainderDeltaKernel, remainderScanKernel); ComputeRemaindersOnGpu(effectiveCount, remainder8, 8, gapView, deltaView, remainder8View, remainderDeltaKernel, remainderScanKernel); ComputeRemaindersOnGpu(effectiveCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel); @@ -482,20 +484,27 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void GenerateCandidatesOnGpu( + private static GpuUInt128 GenerateCandidatesOnGpu( int count, GpuUInt128 startValue, GpuUInt128 stride, ArrayView1D candidateView, ArrayView1D gapView, - Action, ArrayView, GpuUInt128, GpuUInt128> kernel) + ArrayView1D nextStartView, + Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> kernel) { - if (count <= 0) - { - return; - } + // EvenPerfectBitScanner always launches this helper with a positive count, + // so the guard stays commented out to keep the hot path branch-free. + // if (count <= 0) + // { + // return GpuUInt128.Zero; + // } - kernel(count, candidateView, gapView, startValue, stride); + kernel(count, candidateView, gapView, nextStartView, startValue, stride); + + GpuUInt128 nextStart = default; + nextStartView.SubView(0, 1).CopyToCPU(ref nextStart, 1); + return nextStart; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -509,10 +518,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv Action, ArrayView, byte> deltaKernel, Action, ArrayView, byte, byte> scanKernel) { - if (count <= 0) - { - return; - } + // EvenPerfectBitScanner always supplies a positive count here, + // so the guard stays commented out to keep the hot path branch-free. + // if (count <= 0) + // { + // return; + // } deltaKernel(count, gapView, deltaView, modulus); SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic(count); @@ -760,6 +771,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ExponentBuffer = accelerator.Allocate1D(actualCapacity); HitsBuffer = accelerator.Allocate1D(actualCapacity); CandidateBuffer = accelerator.Allocate1D(actualCapacity); + NextStartBuffer = accelerator.Allocate1D(1); DivisorDeltaBuffer = accelerator.Allocate1D(actualCapacity); RemainderDeltaBuffer = accelerator.Allocate1D(actualCapacity); RemainderBuffer = accelerator.Allocate1D(actualCapacity * RemainderSlotCount); @@ -786,6 +798,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal readonly MemoryBuffer1D CandidateBuffer; + internal readonly MemoryBuffer1D NextStartBuffer; + internal readonly MemoryBuffer1D DivisorDeltaBuffer; internal readonly MemoryBuffer1D RemainderDeltaBuffer; @@ -808,7 +822,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal readonly Action, ArrayView, byte, byte> RemainderScanKernel; internal readonly Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> CandidateMaskKernel; - internal readonly Action, ArrayView, GpuUInt128, GpuUInt128> CandidateGenerationKernel; + internal readonly Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> CandidateGenerationKernel; internal readonly int Capacity; @@ -820,6 +834,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ExponentBuffer.Dispose(); HitsBuffer.Dispose(); CandidateBuffer.Dispose(); + NextStartBuffer.Dispose(); DivisorDeltaBuffer.Dispose(); RemainderDeltaBuffer.Dispose(); RemainderBuffer.Dispose();