From ea0158589246e03480ef4dfe44b4cddd1ea6f35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 23 Oct 2025 00:34:17 +0200 Subject: [PATCH] Convert divisor session results to hits on the GPU --- .../MersenneNumberDivisorGpuTesterTests.cs | 24 +------- .../Gpu/Kernels/DivisorByDivisorKernels.cs | 18 ++++++ ...MersenneNumberDivisorByDivisorGpuTester.cs | 56 ++++++++++++------- 3 files changed, 56 insertions(+), 42 deletions(-) diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs index 29988a2..8f67d06 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs @@ -197,17 +197,7 @@ public class MersenneNumberDivisorGpuTesterTests session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); - var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession) - .GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!; - ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!; - Span residues = hostBuffer.AsSpan(0, exponents.Length); - byte[] computedHits = new byte[exponents.Length]; - for (int i = 0; i < residues.Length; i++) - { - computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0; - } - - computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); } [Fact] @@ -229,17 +219,7 @@ public class MersenneNumberDivisorGpuTesterTests session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); - var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession) - .GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!; - ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!; - Span residues = hostBuffer.AsSpan(0, exponents.Length); - byte[] computedHits = new byte[exponents.Length]; - for (int i = 0; i < residues.Length; i++) - { - computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0; - } - - computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); } [Fact] diff --git a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs index b15a091..1540c09 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/DivisorByDivisorKernels.cs @@ -255,6 +255,24 @@ internal static class DivisorByDivisorKernels Atomic.Min(ref firstHit[0], candidate); } + public static void ConvertMontgomeryResultsToHitsKernel( + Index1D index, + ArrayView results, + ArrayView hits) + { + int globalIndex = index; + // EvenPerfectBitScanner launches this kernel with an extent matching the result and hit spans, + // so the defensive bounds guard stays commented out to keep the conversion branch-free. + // int length = (int)results.Length; + // if (globalIndex >= length) + // { + // return; + // } + + ulong montgomeryResult = results[globalIndex]; + hits[globalIndex] = montgomeryResult == 1UL ? (byte)1 : (byte)0; + } + 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 342a935..0af37c0 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -30,6 +30,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv 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>> _hitConversionKernelCache = 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(); @@ -64,6 +65,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView>(DivisorByDivisorKernels.PublishFirstHitKernel)); + private Action, ArrayView> GetResultConversionKernel(Accelerator accelerator) => + _hitConversionKernelCache.GetOrAdd( + accelerator, + acc => acc.LoadAutoGroupedStreamKernel, ArrayView>(DivisorByDivisorKernels.ConvertMontgomeryResultsToHitsKernel)); + private Action, ArrayView, ArrayView, GpuUInt128, GpuUInt128> GetCandidateGenerationKernel(Accelerator accelerator) => _candidateGenerationKernelCache.GetOrAdd( accelerator, @@ -686,10 +692,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private readonly MersenneNumberDivisorByDivisorGpuTester _owner; private readonly GpuContextPool.GpuContextLease _lease; private readonly Accelerator _accelerator; - private Action, ArrayView> _kernel; - private MemoryBuffer1D _exponentsBuffer; - private MemoryBuffer1D _resultsBuffer; - private ulong[] _hostBuffer; + private Action, ArrayView> _kernel = null!; + private Action, ArrayView> _hitConversionKernel = null!; + private MemoryBuffer1D _exponentsBuffer = null!; + private MemoryBuffer1D _resultsBuffer = null!; + private MemoryBuffer1D _hitBuffer = null!; + private ulong[] _hostBuffer = null!; private int _capacity; private bool _disposed; @@ -709,8 +717,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private void EnsureExecutionResourcesLocked() { _kernel = _owner.GetKernelByPrimeExponent(_accelerator); + _hitConversionKernel = _owner.GetResultConversionKernel(_accelerator); _exponentsBuffer = _accelerator.Allocate1D(_capacity); _resultsBuffer = _accelerator.Allocate1D(_capacity); + _hitBuffer = _accelerator.Allocate1D(_capacity); _hostBuffer = ThreadStaticPools.UlongPool.Rent(_capacity); } @@ -722,26 +732,33 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv } int length = primes.Length; - if (length == 0) - { - return; - } + // EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the previous zero-length guard stays commented out. + // if (length == 0) + // { + // return; + // } - if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL) - { - hits.Clear(); - return; - } + // The GPU divisor sessions only materialize odd moduli greater than one (q = 2kp + 1), + // so the defensive modulus guard stays commented out to keep the hot path branch-free. + // if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL) + // { + // hits.Clear(); + // return; + // } Monitor.Enter(_lease.ExecutionLock); - if (_kernel == null) + if (_kernel == null || _hitConversionKernel == null) { EnsureExecutionResourcesLocked(); } + var kernel = _kernel!; + var hitConversionKernel = _hitConversionKernel!; + ArrayView1D exponentsView = _exponentsBuffer.View; ArrayView1D resultsView = _resultsBuffer.View; + ArrayView1D hitView = _hitBuffer.View; Span hostSpan = _hostBuffer.AsSpan(0, _capacity); int offset = 0; @@ -755,14 +772,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv exponentSlice.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), batchSize); ArrayView1D resultSlice = resultsView.SubView(0, batchSize); - _kernel(batchSize, divisorData, exponentSlice, resultSlice); - resultSlice.CopyToCPU(ref MemoryMarshal.GetReference(hostSpan), batchSize); + kernel(batchSize, divisorData, exponentSlice, resultSlice); + + ArrayView1D hitSliceView = hitView.SubView(0, batchSize); + hitConversionKernel(batchSize, resultSlice, hitSliceView); Span hitSlice = hits.Slice(offset, batchSize); - for (int i = 0; i < batchSize; i++) - { - hitSlice[i] = hostSpan[i] == 1UL ? (byte)1 : (byte)0; - } + hitSliceView.CopyToCPU(ref MemoryMarshal.GetReference(hitSlice), batchSize); offset += batchSize; }