From faab60c2cd78aae5825bd3fbbafb2ddf2451bb66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 22 Oct 2025 23:51:19 +0200 Subject: [PATCH] Reduce host hit detection overhead --- .../Gpu/Kernels/DivisorByDivisorKernels.cs | 19 +++++++ ...MersenneNumberDivisorByDivisorGpuTester.cs | 53 +++++++++++++++---- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs index e7546cc..b15a091 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs @@ -236,6 +236,25 @@ internal static class DivisorByDivisorKernels Atomic.Max(ref compactedCount[0], storedCount); } + public static void PublishFirstHitKernel( + Index1D index, + ArrayView hits, + ArrayView firstHit) + { + int globalIndex = index; + // EvenPerfectBitScanner launches this auto-grouped kernel with an extent matching the hit view length, + // so the defensive guard stays commented out to keep the hot path branch-free. + // int length = (int)hits.Length; + // if (globalIndex >= length) + // { + // return; + // } + + byte hitValue = hits[globalIndex]; + int candidate = hitValue != 0 ? globalIndex : int.MaxValue; + Atomic.Min(ref firstHit[0], candidate); + } + public static void ComputeMontgomeryExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView exponents, ArrayView results) { ulong modulus = divisor.Modulus; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 6dd5c47..342a935 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -29,6 +29,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv 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, ArrayView, ArrayView>> _candidateCompactionKernelCache = new(AcceleratorReferenceComparer.Instance); + private readonly ConcurrentDictionary, ArrayView>> _hitDetectionKernelCache = 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(); @@ -58,6 +59,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv accelerator, acc => acc.LoadStreamKernel, ArrayView, ArrayView, ArrayView>(DivisorByDivisorKernels.CompactCandidateMaskKernel)); + private Action, ArrayView> GetHitDetectionKernel(Accelerator accelerator) => + _hitDetectionKernelCache.GetOrAdd( + accelerator, + acc => acc.LoadAutoGroupedStreamKernel, ArrayView>(DivisorByDivisorKernels.PublishFirstHitKernel)); + private Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> GetCandidateGenerationKernel(Accelerator accelerator) => _candidateGenerationKernelCache.GetOrAdd( accelerator, @@ -137,6 +143,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv resources.DivisorsBuffer, resources.ExponentBuffer, resources.HitsBuffer, + resources.HitIndexBuffer, resources.CandidateBuffer, resources.NextStartBuffer, resources.DivisorDeltaBuffer, @@ -148,9 +155,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv resources.CandidateMaskKernel, resources.CandidateCompactionKernel, resources.CandidateGenerationKernel, + resources.HitDetectionKernel, resources.Divisors, resources.Exponents, - resources.Hits, resources.DivisorData, resources.CycleCandidates, resources.CycleLengths, @@ -210,6 +217,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv MemoryBuffer1D divisorsBuffer, MemoryBuffer1D exponentBuffer, MemoryBuffer1D hitsBuffer, + MemoryBuffer1D hitIndexBuffer, MemoryBuffer1D candidateBuffer, MemoryBuffer1D nextStartBuffer, MemoryBuffer1D divisorDeltaBuffer, @@ -221,9 +229,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> candidateMaskKernel, Action, ArrayView, ArrayView, ArrayView> candidateCompactionKernel, Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> candidateGenerationKernel, + Action, ArrayView> hitDetectionKernel, in ulong[] divisors, ulong[] exponents, - byte[] hits, MontgomeryDivisorData[] divisorData, ulong[] cycleCandidates, ulong[] cycleLengths, @@ -282,7 +290,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ulong[] filteredDivisors = ulongPool.Rent(chunkCapacity); var viewCache = new BufferSliceCache(divisorDeltaBuffer, remainderDeltaBuffer, remainderBuffer, hitsBuffer, remainderStride); Span candidateStorage = cycleCandidates.AsSpan(); - Span hitStorage = hits.AsSpan(); Span filteredStorage = filteredDivisors.AsSpan(); Span cycleLengthStorage = cycleLengths.AsSpan(); Span divisorDataStorage = divisorData.AsSpan(); @@ -399,10 +406,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv Span divisorDataSpan = divisorDataStorage[..admissibleCount]; Span divisorSpan = divisorStorage[..admissibleCount]; Span exponentSpan = exponentStorage[..admissibleCount]; - Span hitsSpan = hitStorage[..admissibleCount]; ArrayView1D divisorView = divisorsBuffer.View.SubView(0, admissibleCount); ArrayView1D exponentView = exponentBuffer.View.SubView(0, admissibleCount); ArrayView1D hitsView = hitsBuffer.View.SubView(0, admissibleCount); + ArrayView1D hitIndexView = hitIndexBuffer.View.SubView(0, 1); for (int i = 0; i < admissibleCount; i++) { @@ -415,9 +422,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount); exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), admissibleCount); kernel(admissibleCount, divisorView, exponentView, hitsView); - hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), admissibleCount); - int hitIndex = System.MemoryExtensions.IndexOf(hitsSpan, (byte)1); + int hitIndex = FindFirstHitOnGpu(admissibleCount, hitsView, hitIndexView, hitDetectionKernel); bool hitFound = hitIndex >= 0; composite = hitFound; int lastIndex = admissibleCount - 1; @@ -601,6 +607,30 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv return filteredCount; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FindFirstHitOnGpu( + int count, + ArrayView1D hitsView, + ArrayView1D hitIndexView, + Action, ArrayView> hitKernel) + { + // EvenPerfectBitScanner only calls this helper with positive counts, + // so the guard stays commented out to keep the hot path branch-free. + // if (count <= 0) + // { + // return -1; + // } + + int sentinel = int.MaxValue; + hitIndexView.SubView(0, 1).CopyFromCPU(ref sentinel, 1); + + hitKernel(count, hitsView, hitIndexView); + + int firstHit = sentinel; + hitIndexView.SubView(0, 1).CopyToCPU(ref firstHit, 1); + return firstHit >= count ? -1 : firstHit; + } + private static byte FetchLastRemainder(ArrayView1D remainderView, int count) { // Callers guard against zero counts before invoking this helper to keep the hot path branch-free. @@ -811,6 +841,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv DivisorsBuffer = accelerator.Allocate1D(actualCapacity); ExponentBuffer = accelerator.Allocate1D(actualCapacity); HitsBuffer = accelerator.Allocate1D(actualCapacity); + HitIndexBuffer = accelerator.Allocate1D(1); CandidateBuffer = accelerator.Allocate1D(actualCapacity); NextStartBuffer = accelerator.Allocate1D(1); DivisorDeltaBuffer = accelerator.Allocate1D(actualCapacity); @@ -820,7 +851,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ArrayPool ulongPool = ThreadStaticPools.UlongPool; Divisors = ulongPool.Rent(actualCapacity); Exponents = ulongPool.Rent(actualCapacity); - Hits = ThreadStaticPools.BytePool.Rent(actualCapacity); DivisorData = ThreadStaticPools.MontgomeryDivisorDataPool.Rent(actualCapacity); CycleCandidates = ulongPool.Rent(cycleCapacity); CycleLengths = ulongPool.Rent(cycleCapacity); @@ -829,6 +859,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv CandidateMaskKernel = owner.GetCandidateMaskKernel(accelerator); CandidateCompactionKernel = owner.GetCandidateCompactionKernel(accelerator); CandidateGenerationKernel = owner.GetCandidateGenerationKernel(accelerator); + HitDetectionKernel = owner.GetHitDetectionKernel(accelerator); Capacity = actualCapacity; CycleCapacity = cycleCapacity; } @@ -839,6 +870,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal readonly MemoryBuffer1D HitsBuffer; + internal readonly MemoryBuffer1D HitIndexBuffer; + internal readonly MemoryBuffer1D CandidateBuffer; internal readonly MemoryBuffer1D NextStartBuffer; @@ -854,8 +887,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal readonly ulong[] Exponents; - internal readonly byte[] Hits; - internal readonly MontgomeryDivisorData[] DivisorData; internal readonly ulong[] CycleCandidates; @@ -868,6 +899,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal readonly Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> CandidateMaskKernel; internal readonly Action, ArrayView, ArrayView, ArrayView> CandidateCompactionKernel; internal readonly Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> CandidateGenerationKernel; + internal readonly Action, ArrayView> HitDetectionKernel; internal readonly int Capacity; @@ -878,6 +910,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv DivisorsBuffer.Dispose(); ExponentBuffer.Dispose(); HitsBuffer.Dispose(); + HitIndexBuffer.Dispose(); CandidateBuffer.Dispose(); NextStartBuffer.Dispose(); DivisorDeltaBuffer.Dispose(); @@ -885,11 +918,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv RemainderBuffer.Dispose(); FilterCountBuffer.Dispose(); ArrayPool ulongPool = ThreadStaticPools.UlongPool; - ArrayPool bytePool = ThreadStaticPools.BytePool; ArrayPool divisorPool = ThreadStaticPools.MontgomeryDivisorDataPool; ulongPool.Return(Divisors, clearArray: false); ulongPool.Return(Exponents, clearArray: false); - bytePool.Return(Hits, clearArray: false); divisorPool.Return(DivisorData, clearArray: false); ulongPool.Return(CycleCandidates, clearArray: false); ulongPool.Return(CycleLengths, clearArray: false);