diff --git a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs index 04f67f7..7e6258f 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs @@ -155,6 +155,63 @@ internal static class DivisorByDivisorKernels mask[globalIndex] = accepted; } + public static void CompactCandidateMaskKernel( + Index1D index, + ArrayView mask, + ArrayView sourceCandidates, + ArrayView compactedCandidates, + ArrayView compactedCount) + { + int globalIndex = index; + int length = (int)mask.Length; + if (globalIndex >= length) + { + return; + } + + int localIndex = Group.IdxX; + int groupSize = Group.Dimension.X; + var shared = SharedMemory.GetDynamic(); + + byte maskValue = mask[globalIndex]; + int accepted = maskValue != 0 ? 1 : 0; + shared[localIndex] = accepted; + + ulong candidateValue = sourceCandidates[globalIndex]; + + Group.Barrier(); + + int offset = 1; + while (offset < groupSize) + { + int addend = localIndex >= offset ? shared[localIndex - offset] : 0; + + Group.Barrier(); + + int current = shared[localIndex]; + int sum = current + addend; + if (localIndex >= offset) + { + shared[localIndex] = sum; + } + + Group.Barrier(); + + offset <<= 1; + } + + if (accepted != 0) + { + int targetIndex = shared[localIndex] - 1; + compactedCandidates[targetIndex] = candidateValue; + } + + if (globalIndex == length - 1) + { + compactedCount[0] = shared[localIndex]; + } + } + 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 70e1292..a5b7955 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -28,6 +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, ArrayView, ArrayView>> _candidateCompactionKernelCache = 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(); @@ -52,6 +53,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, byte, ArrayView>(DivisorByDivisorKernels.EvaluateCandidateMaskKernel)); + private Action, ArrayView, ArrayView, ArrayView> GetCandidateCompactionKernel(Accelerator accelerator) => + _candidateCompactionKernelCache.GetOrAdd( + accelerator, + acc => acc.LoadStreamKernel, ArrayView, ArrayView, ArrayView>(DivisorByDivisorKernels.CompactCandidateMaskKernel)); + private Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> GetCandidateGenerationKernel(Accelerator accelerator) => _candidateGenerationKernelCache.GetOrAdd( accelerator, @@ -136,9 +142,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv resources.DivisorDeltaBuffer, resources.RemainderDeltaBuffer, resources.RemainderBuffer, + resources.FilterCountBuffer, resources.RemainderDeltaKernel, resources.RemainderScanKernel, resources.CandidateMaskKernel, + resources.CandidateCompactionKernel, resources.CandidateGenerationKernel, resources.Divisors, resources.Exponents, @@ -207,9 +215,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv MemoryBuffer1D divisorDeltaBuffer, MemoryBuffer1D remainderDeltaBuffer, MemoryBuffer1D remainderBuffer, + MemoryBuffer1D filterCountBuffer, Action, ArrayView, byte> remainderDeltaKernel, Action, ArrayView, byte, byte> remainderScanKernel, Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> candidateMaskKernel, + Action, ArrayView, ArrayView, ArrayView> candidateCompactionKernel, Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> candidateGenerationKernel, in ulong[] divisors, ulong[] exponents, @@ -293,7 +303,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv } Span candidateSpan = candidateStorage[..effectiveCount]; - Span maskSpan = hitStorage[..effectiveCount]; viewCache.Ensure(effectiveCount); @@ -304,6 +313,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ArrayView1D remainder5View = viewCache.Remainder5; ArrayView1D remainder3View = viewCache.Remainder3; ArrayView1D maskView = viewCache.Hits; + ArrayView1D filteredCountView = filterCountBuffer.View; UInt128 localDivisor = currentDivisor128; @@ -332,7 +342,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ComputeRemaindersOnGpu(effectiveCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel); ComputeRemaindersOnGpu(effectiveCount, remainder3, 3, gapView, deltaView, remainder3View, remainderDeltaKernel, remainderScanKernel); - BuildCandidateMaskOnGpu(effectiveCount, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView, maskSpan, candidateMaskKernel); + BuildCandidateMaskOnGpu(effectiveCount, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView, candidateMaskKernel); byte lastRemainder10 = FetchLastRemainder(remainder10View, effectiveCount); byte lastRemainder8 = FetchLastRemainder(remainder8View, effectiveCount); @@ -344,14 +354,21 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv remainder5 = AddModGpu(lastRemainder5, step5, 5); remainder3 = AddModGpu(lastRemainder3, step3, 3); - int filteredCount = 0; - Span filteredDivisorsSpan = filteredStorage[..effectiveCount]; + int filteredCount = CompactCandidatesOnGpu( + effectiveCount, + maskView, + candidateView, + candidateView, + filteredCountView, + candidateCompactionKernel); - for (int i = 0; i < effectiveCount; i++) + if (filteredCount > 0) { - int add = maskSpan[i] == 0 ? 0 : 1; - filteredDivisorsSpan[filteredCount] = candidateSpan[i]; - filteredCount += add; + Span filteredSlice = filteredStorage[..filteredCount]; + ArrayView1D filteredView = candidateView.SubView(0, filteredCount); + // The compaction kernel rewrites the accepted divisors into the front of the candidate buffer, + // so copying the compacted prefix is enough to build the CPU span for cycle checks. + filteredView.CopyToCPU(ref MemoryMarshal.GetReference(filteredSlice), filteredCount); } currentK += (ulong)effectiveCount; @@ -362,8 +379,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv continue; } + Span filteredDivisorsSpan = filteredStorage[..filteredCount]; Span cycleSpan = cycleLengthStorage[..filteredCount]; - cycleCache.GetCycleLengths(filteredDivisorsSpan[..filteredCount], cycleSpan); + cycleCache.GetCycleLengths(filteredDivisorsSpan, cycleSpan); int admissibleCount = 0; for (int i = 0; i < filteredCount; i++) @@ -543,17 +561,39 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ArrayView1D remainder3View, byte lastIsSevenFlag, ArrayView1D maskView, - Span destination, Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> maskKernel) { if (count <= 0) { - destination.Clear(); return; } maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView); - maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count); + } + + private static int CompactCandidatesOnGpu( + int count, + ArrayView1D maskView, + ArrayView1D sourceView, + ArrayView1D destinationView, + ArrayView1D countView, + Action, ArrayView, ArrayView, ArrayView> compactionKernel) + { + if (count <= 0) + { + return 0; + } + + SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic(count); + Index1D gridDim = new Index1D(1); + Index1D groupDim = new Index1D(count); + KernelConfig kernelConfig = new KernelConfig(gridDim, groupDim, sharedMemoryConfig); + + compactionKernel(kernelConfig, new Index1D(count), maskView, sourceView, destinationView, countView); + + int filteredCount = 0; + countView.SubView(0, 1).CopyToCPU(ref filteredCount, 1); + return filteredCount; } private static byte FetchLastRemainder(ArrayView1D remainderView, int count) @@ -775,6 +815,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv DivisorDeltaBuffer = accelerator.Allocate1D(actualCapacity); RemainderDeltaBuffer = accelerator.Allocate1D(actualCapacity); RemainderBuffer = accelerator.Allocate1D(actualCapacity * RemainderSlotCount); + FilterCountBuffer = accelerator.Allocate1D(1); ArrayPool ulongPool = ThreadStaticPools.UlongPool; Divisors = ulongPool.Rent(actualCapacity); Exponents = ulongPool.Rent(actualCapacity); @@ -785,6 +826,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv RemainderDeltaKernel = owner.GetRemainderDeltaKernel(accelerator); RemainderScanKernel = owner.GetRemainderScanKernel(accelerator); CandidateMaskKernel = owner.GetCandidateMaskKernel(accelerator); + CandidateCompactionKernel = owner.GetCandidateCompactionKernel(accelerator); CandidateGenerationKernel = owner.GetCandidateGenerationKernel(accelerator); Capacity = actualCapacity; CycleCapacity = cycleCapacity; @@ -805,6 +847,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal readonly MemoryBuffer1D RemainderDeltaBuffer; internal readonly MemoryBuffer1D RemainderBuffer; + internal readonly MemoryBuffer1D FilterCountBuffer; internal readonly ulong[] Divisors; @@ -822,6 +865,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, ArrayView, ArrayView> CandidateCompactionKernel; internal readonly Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> CandidateGenerationKernel; internal readonly int Capacity; @@ -838,6 +882,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv DivisorDeltaBuffer.Dispose(); RemainderDeltaBuffer.Dispose(); RemainderBuffer.Dispose(); + FilterCountBuffer.Dispose(); ArrayPool ulongPool = ThreadStaticPools.UlongPool; ArrayPool bytePool = ThreadStaticPools.BytePool; ArrayPool divisorPool = ThreadStaticPools.MontgomeryDivisorDataPool;