Unify GPU divisor kernels with stepper path

This commit is contained in:
Marek Lemańczyk committed 2025-10-24 20:50:52 +02:00
1 parent 868c7a4fd2
commit 4e9d57df4b
4 files changed
+243 -127

No files matched your search

@@ -74,10 +74,6 @@ public class GpuKernelCompilationTests
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance) typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernel not found."); ?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernel not found.");
private static readonly MethodInfo DivisorByDivisorGetExponentKernelMethod =
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetStepperKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernelByPrimeExponent not found.");
private static readonly MethodInfo DivisorTesterGetKernelMethod = private static readonly MethodInfo DivisorTesterGetKernelMethod =
typeof(MersenneNumberDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance) typeof(MersenneNumberDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
?? throw new InvalidOperationException("MersenneNumberDivisorGpuTester.GetKernel not found."); ?? throw new InvalidOperationException("MersenneNumberDivisorGpuTester.GetKernel not found.");
@@ -149,7 +145,6 @@ public class GpuKernelCompilationTests
yield return Loader("NttGpuMath.ForwardKernel", CompileNttForwardKernel); yield return Loader("NttGpuMath.ForwardKernel", CompileNttForwardKernel);
yield return Loader("NttGpuMath.InverseKernel", CompileNttInverseKernel); yield return Loader("NttGpuMath.InverseKernel", CompileNttInverseKernel);
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.CheckKernel", CompileDivisorByDivisorCheckKernel); yield return Loader("MersenneNumberDivisorByDivisorGpuTester.CheckKernel", CompileDivisorByDivisorCheckKernel);
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.EvaluateDivisorWithStepperKernel", CompileDivisorByDivisorExponentKernel);
yield return Loader("MersenneNumberDivisorGpuTester.Kernel", CompileDivisorTesterKernel); yield return Loader("MersenneNumberDivisorGpuTester.Kernel", CompileDivisorTesterKernel);
yield return Loader("MersenneNumberLucasLehmerGpuTester.Kernel", CompileLucasLehmerKernel); yield return Loader("MersenneNumberLucasLehmerGpuTester.Kernel", CompileLucasLehmerKernel);
yield return Loader("MersenneNumberLucasLehmerGpuTester.AddSmallKernel", CompileLucasLehmerAddSmallKernel); yield return Loader("MersenneNumberLucasLehmerGpuTester.AddSmallKernel", CompileLucasLehmerAddSmallKernel);
@@ -271,12 +266,6 @@ public class GpuKernelCompilationTests
_ = DivisorByDivisorGetKernelMethod.Invoke(tester, new object[] { accelerator }); _ = DivisorByDivisorGetKernelMethod.Invoke(tester, new object[] { accelerator });
} }
private static void CompileDivisorByDivisorExponentKernel(Accelerator accelerator)
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
_ = DivisorByDivisorGetExponentKernelMethod.Invoke(tester, new object[] { accelerator });
}
private static void CompileDivisorTesterKernel(Accelerator accelerator) private static void CompileDivisorTesterKernel(Accelerator accelerator)
{ {
var tester = new MersenneNumberDivisorGpuTester(); var tester = new MersenneNumberDivisorGpuTester();
@@ -2,7 +2,6 @@ using ILGPU;
using ILGPU.Algorithms; using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -10,77 +9,80 @@ internal static class DivisorByDivisorKernels
{ {
public static void CheckKernel( public static void CheckKernel(
Index1D index, Index1D index,
ArrayView<ulong> divisors, ArrayView<GpuDivisorPartialData> divisors,
ArrayView<int> exponentOffsets,
ArrayView<int> exponentCounts,
ArrayView<ulong> exponents, ArrayView<ulong> exponents,
ArrayView<ulong> divisorCycles,
ArrayView<ulong> firstCycleRemainders,
ArrayView<byte> hits, ArrayView<byte> hits,
ArrayView<int> firstHit) ArrayView<int> firstHit)
{ {
int globalIndex = index; int globalIndex = index;
ulong modulus = divisors[globalIndex]; int count = exponentCounts[globalIndex];
ulong exponent = exponents[globalIndex]; if (count <= 0)
ulong result = ULongExtensions.Pow2ModWindowedGpuKernel(exponent, modulus); {
byte hit = result == 1UL ? (byte)1 : (byte)0; return;
hits[globalIndex] = hit; }
if (hit != 0) int offset = exponentOffsets[globalIndex];
GpuDivisorPartialData divisor = divisors[globalIndex];
ulong cycleLength = divisorCycles[globalIndex];
ulong firstCycleRemainder = firstCycleRemainders[globalIndex];
var stepper = new ExponentRemainderStepperGpu(divisor);
ulong firstExponent = exponents[offset];
bool firstUnity = stepper.InitializeIsUnityGpu(firstExponent);
byte firstHitValue;
ulong previousExponent = firstExponent;
ulong cycleRemainder = 0UL;
if (cycleLength == 0UL)
{
firstHitValue = firstUnity ? (byte)1 : (byte)0;
}
else
{
cycleRemainder = firstCycleRemainder;
firstHitValue = cycleRemainder == 0UL && firstUnity ? (byte)1 : (byte)0;
}
hits[offset] = firstHitValue;
byte anyHit = firstHitValue;
for (int i = 1; i < count; i++)
{
ulong exponent = exponents[offset + i];
ulong delta = exponent - previousExponent;
previousExponent = exponent;
if (cycleLength != 0UL)
{
var remainderValue = new GpuUInt128(cycleRemainder);
remainderValue.AddMod(delta, cycleLength);
cycleRemainder = remainderValue.Low;
byte hit = 0;
if (cycleRemainder == 0UL)
{
hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0;
}
hits[offset + i] = hit;
anyHit |= hit;
}
else
{
byte hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0;
hits[offset + i] = hit;
anyHit |= hit;
}
}
if (anyHit != 0 && firstHit.Length > 0)
{ {
Atomic.Min(ref firstHit[0], globalIndex); Atomic.Min(ref firstHit[0], globalIndex);
} }
} }
public static void EvaluateDivisorWithStepperKernel(
Index1D _,
GpuDivisorPartialData divisor,
ulong divisorCycle,
ulong firstCycleRemainder,
ArrayView<ulong> exponents,
ArrayView<byte> hits)
{
int length = (int)exponents.Length;
// EvenPerfectBitScanner always supplies at least one exponent per GPU divisor check,
// so the guard stays commented out to keep the kernel branch-free.
// if (length == 0)
// {
// return;
// }
var exponentStepper = new ExponentRemainderStepperGpu(divisor);
ulong firstPrime = exponents[0];
bool firstUnity = exponentStepper.InitializeIsUnityGpu(firstPrime);
// EvenPerfectBitScanner only schedules divisors with non-zero cycle lengths (equal to the tested prime),
// so the no-cycle path remains commented out to keep the kernel branch-free.
// if (divisorCycle == 0UL)
// {
// hits[0] = firstUnity ? (byte)1 : (byte)0;
// for (int i = 1; i < length; i++)
// {
// ulong primeNoCycle = exponents[i];
// bool unityNoCycle = exponentStepper.ComputeNextIsUnityGpu(primeNoCycle);
// hits[i] = unityNoCycle ? (byte)1 : (byte)0;
// }
// return;
// }
ulong cycleLength = divisorCycle;
ulong previousPrime = firstPrime;
// The host precomputes the initial cycle remainder to keep the kernel free of modulo operations.
ulong cycleRemainder = firstCycleRemainder;
hits[0] = cycleRemainder == 0UL && firstUnity ? (byte)1 : (byte)0;
for (int i = 1; i < length; i++)
{
ulong prime = exponents[i];
ulong delta = prime - previousPrime;
previousPrime = prime;
var remainderValue = new GpuUInt128(cycleRemainder);
remainderValue.AddMod(delta, cycleLength);
cycleRemainder = remainderValue.Low;
hits[i] = cycleRemainder == 0UL
? (exponentStepper.ComputeNextIsUnityGpu(prime) ? (byte)1 : (byte)0)
: (byte)0;
}
}
} }
@@ -23,26 +23,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private ulong _divisorLimit; private ulong _divisorLimit;
// private bool _isConfigured; // private bool _isConfigured;
private delegate void StepperKernel(Index1D index, in GpuDivisorPartialData divisor, ulong divisorCycle, ulong firstCycleRemainder, ArrayView<ulong> exponents, ArrayView<byte> hits); private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>> _kernelCache = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>> _kernelCache = new();
private readonly ConcurrentDictionary<Accelerator, StepperKernel> _stepperKernelCache = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance); private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentBag<GpuContextPool.GpuContextLease> _acceleratorPool = new(); private readonly ConcurrentBag<GpuContextPool.GpuContextLease> _acceleratorPool = new();
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new(); private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
private Action<Index1D, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> GetKernel(Accelerator accelerator) => private Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> GetKernel(Accelerator accelerator) =>
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel)); _kernelCache.GetOrAdd(accelerator, acc =>
acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel));
private StepperKernel GetStepperKernel(Accelerator accelerator) =>
_stepperKernelCache.GetOrAdd(accelerator, acc =>
{
var kernel = acc.LoadAutoGroupedStreamKernel<Index1D, GpuDivisorPartialData, ulong, ulong, ArrayView<ulong>, ArrayView<byte>>(DivisorByDivisorKernels.EvaluateDivisorWithStepperKernel);
return (Index1D extent, in GpuDivisorPartialData divisor, ulong divisorCycle, ulong firstCycleRemainder, ArrayView<ulong> exponents, ArrayView<byte> hits) =>
{
kernel(extent, divisor, divisorCycle, firstCycleRemainder, exponents, hits);
};
});
public int GpuBatchSize public int GpuBatchSize
@@ -115,12 +103,21 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
prime, prime,
allowedMax, allowedMax,
kernel, kernel,
resources.DivisorsBuffer, resources.DivisorDataBuffer,
resources.OffsetBuffer,
resources.CountBuffer,
resources.CycleBuffer,
resources.FirstRemainderBuffer,
resources.ExponentBuffer, resources.ExponentBuffer,
resources.HitsBuffer, resources.HitsBuffer,
resources.HitIndexBuffer, resources.HitIndexBuffer,
resources.Divisors, resources.Divisors,
resources.Exponents, resources.Exponents,
resources.DivisorData,
resources.Offsets,
resources.Counts,
resources.Cycles,
resources.FirstRemainders,
out lastProcessed, out lastProcessed,
out coveredRange, out coveredRange,
out processedCount); out processedCount);
@@ -171,18 +168,27 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private static bool CheckDivisors( private static bool CheckDivisors(
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
Action<Index1D, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> kernel, Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> kernel,
MemoryBuffer1D<ulong, Stride1D.Dense> divisorsBuffer, MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> divisorDataBuffer,
MemoryBuffer1D<int, Stride1D.Dense> offsetBuffer,
MemoryBuffer1D<int, Stride1D.Dense> countBuffer,
MemoryBuffer1D<ulong, Stride1D.Dense> cycleBuffer,
MemoryBuffer1D<ulong, Stride1D.Dense> firstRemainderBuffer,
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer, MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer,
MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer, MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer,
MemoryBuffer1D<int, Stride1D.Dense> hitIndexBuffer, MemoryBuffer1D<int, Stride1D.Dense> hitIndexBuffer,
in ulong[] divisors, in ulong[] divisors,
ulong[] exponents, ulong[] exponents,
GpuDivisorPartialData[] divisorData,
int[] offsets,
int[] counts,
ulong[] cycles,
ulong[] firstRemainders,
out ulong lastProcessed, out ulong lastProcessed,
out bool coveredRange, out bool coveredRange,
out ulong processedCount) out ulong processedCount)
{ {
int batchCapacity = (int)divisorsBuffer.Length; int batchCapacity = (int)divisorDataBuffer.Length;
bool composite = false; bool composite = false;
bool processedAll = false; bool processedAll = false;
processedCount = 0UL; processedCount = 0UL;
@@ -228,6 +234,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
Span<ulong> filteredStorage = filteredDivisors.AsSpan(); Span<ulong> filteredStorage = filteredDivisors.AsSpan();
Span<ulong> divisorStorage = divisors.AsSpan(); Span<ulong> divisorStorage = divisors.AsSpan();
Span<ulong> exponentStorage = exponents.AsSpan(); Span<ulong> exponentStorage = exponents.AsSpan();
Span<GpuDivisorPartialData> divisorDataStorage = divisorData.AsSpan();
Span<int> offsetStorage = offsets.AsSpan();
Span<int> countStorage = counts.AsSpan();
Span<ulong> cycleStorage = cycles.AsSpan();
Span<ulong> remainderStorage = firstRemainders.AsSpan();
while (currentK <= maxK && !composite) while (currentK <= maxK && !composite)
{ {
@@ -297,8 +308,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
Span<ulong> divisorSpan = divisorStorage; Span<ulong> divisorSpan = divisorStorage;
Span<ulong> exponentSpan = exponentStorage; Span<ulong> exponentSpan = exponentStorage;
Span<GpuDivisorPartialData> divisorDataSpan = divisorDataStorage;
Span<int> offsetSpan = offsetStorage;
Span<int> countSpan = countStorage;
Span<ulong> cycleSpan = cycleStorage;
Span<ulong> remainderSpan = remainderStorage;
int admissibleCount = 0; int admissibleCount = 0;
int exponentIndex = 0;
for (int i = 0; i < filteredCount; i++) for (int i = 0; i < filteredCount; i++)
{ {
ulong divisorValue = filteredDivisorsSpan[i]; ulong divisorValue = filteredDivisorsSpan[i];
@@ -310,8 +327,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
divisorSpan[admissibleCount] = divisorValue; divisorSpan[admissibleCount] = divisorValue;
exponentSpan[admissibleCount] = prime; exponentSpan[exponentIndex] = prime;
divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue);
offsetSpan[admissibleCount] = exponentIndex;
countSpan[admissibleCount] = 1;
cycleSpan[admissibleCount] = divisorCycle;
remainderSpan[admissibleCount] = 0UL;
admissibleCount++; admissibleCount++;
exponentIndex++;
} }
if (admissibleCount == 0) if (admissibleCount == 0)
@@ -319,27 +343,34 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
continue; continue;
} }
Span<ulong> activeDivisors = divisorSpan[..admissibleCount]; ArrayView1D<GpuDivisorPartialData, Stride1D.Dense> divisorView = divisorDataBuffer.View.SubView(0, admissibleCount);
Span<ulong> activeExponents = exponentSpan[..admissibleCount]; ArrayView1D<int, Stride1D.Dense> offsetView = offsetBuffer.View.SubView(0, admissibleCount);
ArrayView1D<ulong, Stride1D.Dense> divisorView = divisorsBuffer.View.SubView(0, admissibleCount); ArrayView1D<int, Stride1D.Dense> countView = countBuffer.View.SubView(0, admissibleCount);
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentBuffer.View.SubView(0, admissibleCount); ArrayView1D<ulong, Stride1D.Dense> cycleView = cycleBuffer.View.SubView(0, admissibleCount);
ArrayView1D<byte, Stride1D.Dense> hitsView = hitsBuffer.View.SubView(0, admissibleCount); ArrayView1D<ulong, Stride1D.Dense> remainderView = firstRemainderBuffer.View.SubView(0, admissibleCount);
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentBuffer.View.SubView(0, exponentIndex);
ArrayView1D<byte, Stride1D.Dense> hitsView = hitsBuffer.View.SubView(0, exponentIndex);
ArrayView1D<int, Stride1D.Dense> hitIndexView = hitIndexBuffer.View.SubView(0, 1); ArrayView1D<int, Stride1D.Dense> hitIndexView = hitIndexBuffer.View.SubView(0, 1);
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(activeDivisors), admissibleCount); divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount);
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(activeExponents), admissibleCount); offsetView.CopyFromCPU(ref MemoryMarshal.GetReference(offsetSpan), admissibleCount);
int sentinel = int.MaxValue; countView.CopyFromCPU(ref MemoryMarshal.GetReference(countSpan), admissibleCount);
hitIndexView.SubView(0, 1).CopyFromCPU(ref sentinel, 1); cycleView.CopyFromCPU(ref MemoryMarshal.GetReference(cycleSpan), admissibleCount);
remainderView.CopyFromCPU(ref MemoryMarshal.GetReference(remainderSpan), admissibleCount);
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), exponentIndex);
kernel(admissibleCount, divisorView, exponentView, hitsView, hitIndexView); int sentinel = int.MaxValue;
hitIndexView.CopyFromCPU(ref sentinel, 1);
kernel(admissibleCount, divisorView, offsetView, countView, exponentView, cycleView, remainderView, hitsView, hitIndexView);
int firstHit = sentinel; int firstHit = sentinel;
hitIndexView.SubView(0, 1).CopyToCPU(ref firstHit, 1); hitIndexView.CopyToCPU(ref firstHit, 1);
int hitIndex = firstHit >= admissibleCount ? -1 : firstHit; int hitIndex = firstHit >= admissibleCount ? -1 : firstHit;
bool hitFound = hitIndex >= 0; bool hitFound = hitIndex >= 0;
composite = hitFound; composite = hitFound;
int lastIndex = admissibleCount - 1; int lastIndex = admissibleCount - 1;
lastProcessed = hitFound ? activeDivisors[hitIndex] : activeDivisors[lastIndex]; lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex];
} }
ulongPool.Return(filteredDivisors, clearArray: false); ulongPool.Return(filteredDivisors, clearArray: false);
@@ -348,6 +379,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
return composite; return composite;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong ResolveDivisorCycle(ulong divisor, ulong prime, in MontgomeryDivisorData divisorData) private static ulong ResolveDivisorCycle(ulong divisor, ulong prime, in MontgomeryDivisorData divisorData)
{ {
@@ -448,7 +480,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private readonly MersenneNumberDivisorByDivisorGpuTester _owner; private readonly MersenneNumberDivisorByDivisorGpuTester _owner;
private readonly GpuContextPool.GpuContextLease _lease; private readonly GpuContextPool.GpuContextLease _lease;
private readonly Accelerator _accelerator; private readonly Accelerator _accelerator;
private StepperKernel _stepperKernel = null!; private Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!;
private MemoryBuffer1D<ulong, Stride1D.Dense> _cycleBuffer = null!;
private MemoryBuffer1D<ulong, Stride1D.Dense> _firstRemainderBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _firstHitBuffer = null!;
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentsBuffer = null!; private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentsBuffer = null!;
private MemoryBuffer1D<byte, Stride1D.Dense> _hitBuffer = null!; private MemoryBuffer1D<byte, Stride1D.Dense> _hitBuffer = null!;
private ulong[] _hostBuffer = null!; private ulong[] _hostBuffer = null!;
@@ -470,9 +508,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private void EnsureExecutionResourcesLocked(int requiredCapacity) private void EnsureExecutionResourcesLocked(int requiredCapacity)
{ {
if (_stepperKernel == null) if (_kernel == null)
{ {
_stepperKernel = _owner.GetStepperKernel(_accelerator); _kernel = _owner.GetKernel(_accelerator);
}
if (_divisorBuffer == null)
{
_divisorBuffer = _accelerator.Allocate1D<GpuDivisorPartialData>(1);
_offsetBuffer = _accelerator.Allocate1D<int>(1);
_countBuffer = _accelerator.Allocate1D<int>(1);
_cycleBuffer = _accelerator.Allocate1D<ulong>(1);
_firstRemainderBuffer = _accelerator.Allocate1D<ulong>(1);
_firstHitBuffer = _accelerator.Allocate1D<int>(1);
} }
bool allocateBuffers = _exponentsBuffer == null; bool allocateBuffers = _exponentsBuffer == null;
@@ -523,8 +571,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
// return; // return;
// } // }
GpuDivisorPartialData cachedData = new GpuDivisorPartialData(divisor);
ulong cycle = divisorCycle; ulong cycle = divisorCycle;
ulong firstPrime = primes[0]; ulong firstPrime = primes[0];
@@ -545,16 +591,36 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
} }
// Precompute the initial cycle remainder on the host to keep the GPU kernel branch-free. ulong firstRemainder = cycle == 0UL ? 0UL : firstPrime % cycle;
ulong firstRemainder = firstPrime % cycle;
Monitor.Enter(_lease.ExecutionLock); Monitor.Enter(_lease.ExecutionLock);
EnsureExecutionResourcesLocked(length); EnsureExecutionResourcesLocked(length);
ArrayView1D<GpuDivisorPartialData, Stride1D.Dense> divisorView = _divisorBuffer.View;
ArrayView1D<int, Stride1D.Dense> offsetView = _offsetBuffer.View;
ArrayView1D<int, Stride1D.Dense> countView = _countBuffer.View;
ArrayView1D<ulong, Stride1D.Dense> cycleView = _cycleBuffer.View;
ArrayView1D<ulong, Stride1D.Dense> remainderView = _firstRemainderBuffer.View;
ArrayView1D<int, Stride1D.Dense> firstHitView = _firstHitBuffer.View;
ArrayView1D<ulong, Stride1D.Dense> exponentView = _exponentsBuffer.View; ArrayView1D<ulong, Stride1D.Dense> exponentView = _exponentsBuffer.View;
ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View; ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View;
GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor);
divisorView.SubView(0, 1).CopyFromCPU(ref partialData, 1);
int offsetValue = 0;
offsetView.SubView(0, 1).CopyFromCPU(ref offsetValue, 1);
int countValue = length;
countView.SubView(0, 1).CopyFromCPU(ref countValue, 1);
ulong cycleValue = cycle;
cycleView.SubView(0, 1).CopyFromCPU(ref cycleValue, 1);
ulong remainderValue = firstRemainder;
remainderView.SubView(0, 1).CopyFromCPU(ref remainderValue, 1);
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length); Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length);
primes.CopyTo(hostSpan); primes.CopyTo(hostSpan);
@@ -562,16 +628,18 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
exponentSlice.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), length); exponentSlice.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), length);
ArrayView1D<byte, Stride1D.Dense> hitSliceView = hitView.SubView(0, length); ArrayView1D<byte, Stride1D.Dense> hitSliceView = hitView.SubView(0, length);
var stepperKernel = _stepperKernel!;
// EnsureExecutionResourcesLocked binds the GPU stepper kernel during the first session launch, so the null check
// remains commented out to avoid a redundant branch on the production path.
// if (stepperKernel is null)
// {
// Monitor.Exit(_lease.ExecutionLock);
// throw new InvalidOperationException("GPU stepper kernel was not initialized.");
// }
stepperKernel(1, in cachedData, cycle, firstRemainder, exponentSlice, hitSliceView); int sentinel = int.MaxValue;
firstHitView.SubView(0, 1).CopyFromCPU(ref sentinel, 1);
var kernel = _kernel;
if (kernel is null)
{
Monitor.Exit(_lease.ExecutionLock);
throw new InvalidOperationException("GPU divisor kernel was not initialized.");
}
kernel(1, divisorView, offsetView, countView, exponentSlice, cycleView, remainderView, hitSliceView, firstHitView);
Span<byte> hitSlice = hits.Slice(0, length); Span<byte> hitSlice = hits.Slice(0, length);
hitSliceView.CopyToCPU(ref MemoryMarshal.GetReference(hitSlice), length); hitSliceView.CopyToCPU(ref MemoryMarshal.GetReference(hitSlice), length);
@@ -591,6 +659,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
} }
public ulong DivisorLimit public ulong DivisorLimit
{ {
get get
@@ -641,17 +710,38 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
{ {
int actualCapacity = Math.Max(1, capacity); int actualCapacity = Math.Max(1, capacity);
DivisorsBuffer = accelerator.Allocate1D<ulong>(actualCapacity); DivisorDataBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(actualCapacity);
OffsetBuffer = accelerator.Allocate1D<int>(actualCapacity);
CountBuffer = accelerator.Allocate1D<int>(actualCapacity);
CycleBuffer = accelerator.Allocate1D<ulong>(actualCapacity);
FirstRemainderBuffer = accelerator.Allocate1D<ulong>(actualCapacity);
ExponentBuffer = accelerator.Allocate1D<ulong>(actualCapacity); ExponentBuffer = accelerator.Allocate1D<ulong>(actualCapacity);
HitsBuffer = accelerator.Allocate1D<byte>(actualCapacity); HitsBuffer = accelerator.Allocate1D<byte>(actualCapacity);
HitIndexBuffer = accelerator.Allocate1D<int>(1); HitIndexBuffer = accelerator.Allocate1D<int>(1);
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool; ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool;
ArrayPool<int> intPool = ThreadStaticPools.IntPool;
ArrayPool<GpuDivisorPartialData> partialPool = ThreadStaticPools.GpuDivisorPartialDataPool;
Divisors = ulongPool.Rent(actualCapacity); Divisors = ulongPool.Rent(actualCapacity);
Exponents = ulongPool.Rent(actualCapacity); Exponents = ulongPool.Rent(actualCapacity);
DivisorData = partialPool.Rent(actualCapacity);
Offsets = intPool.Rent(actualCapacity);
Counts = intPool.Rent(actualCapacity);
Cycles = ulongPool.Rent(actualCapacity);
FirstRemainders = ulongPool.Rent(actualCapacity);
Capacity = actualCapacity; Capacity = actualCapacity;
} }
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DivisorsBuffer; internal readonly MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> DivisorDataBuffer;
internal readonly MemoryBuffer1D<int, Stride1D.Dense> OffsetBuffer;
internal readonly MemoryBuffer1D<int, Stride1D.Dense> CountBuffer;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> CycleBuffer;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> FirstRemainderBuffer;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> ExponentBuffer; internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> ExponentBuffer;
@@ -663,21 +753,43 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
internal readonly ulong[] Exponents; internal readonly ulong[] Exponents;
internal readonly GpuDivisorPartialData[] DivisorData;
internal readonly int[] Offsets;
internal readonly int[] Counts;
internal readonly ulong[] Cycles;
internal readonly ulong[] FirstRemainders;
internal readonly int Capacity; internal readonly int Capacity;
public void Dispose() public void Dispose()
{ {
DivisorsBuffer.Dispose(); DivisorDataBuffer.Dispose();
OffsetBuffer.Dispose();
CountBuffer.Dispose();
CycleBuffer.Dispose();
FirstRemainderBuffer.Dispose();
ExponentBuffer.Dispose(); ExponentBuffer.Dispose();
HitsBuffer.Dispose(); HitsBuffer.Dispose();
HitIndexBuffer.Dispose(); HitIndexBuffer.Dispose();
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool; ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool;
ArrayPool<int> intPool = ThreadStaticPools.IntPool;
ArrayPool<GpuDivisorPartialData> partialPool = ThreadStaticPools.GpuDivisorPartialDataPool;
ulongPool.Return(Divisors, clearArray: false); ulongPool.Return(Divisors, clearArray: false);
ulongPool.Return(Exponents, clearArray: false); ulongPool.Return(Exponents, clearArray: false);
partialPool.Return(DivisorData, clearArray: false);
intPool.Return(Offsets, clearArray: false);
intPool.Return(Counts, clearArray: false);
ulongPool.Return(Cycles, clearArray: false);
ulongPool.Return(FirstRemainders, clearArray: false);
} }
} }
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator> private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{ {
public static AcceleratorReferenceComparer Instance { get; } = new(); public static AcceleratorReferenceComparer Instance { get; } = new();
+13
View File
@@ -2,6 +2,7 @@ using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core namespace PerfectNumbers.Core
{ {
@@ -102,6 +103,18 @@ namespace PerfectNumbers.Core
} }
} }
[ThreadStatic]
private static ArrayPool<GpuDivisorPartialData>? _gpuDivisorPartialDataPool;
internal static ArrayPool<GpuDivisorPartialData> GpuDivisorPartialDataPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _gpuDivisorPartialDataPool ??= ArrayPool<GpuDivisorPartialData>.Create();
}
}
[ThreadStatic] [ThreadStatic]
private static ArrayPool<int>? _intPool; private static ArrayPool<int>? _intPool;