Remove temporary by-divisor review documents

This commit is contained in:
Marek Lemańczyk committed 2025-10-28 02:01:28 +01:00
1 parent bd0727131f
commit 6dd5cea79f
27 files changed
+498 -600

No files matched your search

-13
View File
@@ -14,8 +14,6 @@ public static class GpuContextPool
internal sealed class PooledContext
{
private bool disposed;
public Context Context { get; }
public Accelerator Accelerator { get; }
public bool IsCpu { get; }
@@ -34,10 +32,6 @@ public static class GpuContextPool
public void Dispose()
{
if (disposed)
{
return;
}
// Ensure all cached GPU buffers for this accelerator are released.
NttGpuMath.ClearCaches(Accelerator);
@@ -48,7 +42,6 @@ public static class GpuContextPool
Accelerator.Dispose();
Context.Dispose();
disposed = true;
}
}
@@ -131,7 +124,6 @@ public static class GpuContextPool
public struct GpuContextLease
{
private readonly PooledContext _ctx;
private bool disposed;
internal GpuContextLease(PooledContext ctx, object kernelInitLock)
{
@@ -150,13 +142,8 @@ public static class GpuContextPool
public void Dispose()
{
if (disposed)
{
return;
}
Return(_ctx);
disposed = true;
}
}
}
+17 -32
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
@@ -9,30 +10,28 @@ public sealed class GpuKernelLease
{
private static readonly ConcurrentQueue<GpuKernelLease> Pool = new();
private IDisposable? _limiter;
private Action? _releaseLimiter;
private GpuContextLease? _gpu;
private KernelContainer? _kernels;
private AcceleratorStream? _stream;
private object? _executionLock;
private bool _disposed;
private GpuKernelLease()
{
}
internal static GpuKernelLease Rent(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels)
internal static GpuKernelLease Rent(GpuPrimeWorkLimiter.Lease limiter, GpuContextLease gpu, KernelContainer kernels)
{
if (!Pool.TryDequeue(out var lease))
{
lease = new GpuKernelLease();
}
lease._limiter = limiter;
lease._releaseLimiter = limiter.Dispose;
lease._gpu = gpu;
lease._kernels = kernels;
lease._stream = null;
lease._executionLock = gpu.ExecutionLock;
lease._disposed = false;
return lease;
}
@@ -158,35 +157,21 @@ public sealed class GpuKernelLease
}
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_stream?.Dispose();
_stream = null;
_gpu?.Dispose();
_gpu = null;
_executionLock = null;
_limiter?.Dispose();
_limiter = null;
}
_kernels = null;
_disposed = true;
Pool.Enqueue(this);
}
public void Dispose()
{
Dispose(disposing: false);
_stream?.Dispose();
_stream = null;
_gpu?.Dispose();
_gpu = null;
_executionLock = null;
_releaseLimiter?.Invoke();
_releaseLimiter = null;
_kernels = null;
Pool.Enqueue(this);
}
public readonly struct ExecutionScope
+5 -12
View File
@@ -6,13 +6,13 @@ public static class GpuPrimeWorkLimiter
private static readonly object _lock = new();
private static int _currentLimit = 1;
public static IDisposable Acquire()
public static Lease Acquire()
{
// TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
// GPU jobs stop allocating Releaser instances on every acquisition.
// GPU jobs stop allocating Lease instances on every acquisition.
var sem = _semaphore;
sem.Wait();
return new Releaser(sem);
return new Lease(sem);
}
public static void SetLimit(int value)
@@ -36,25 +36,18 @@ public static class GpuPrimeWorkLimiter
}
}
private sealed class Releaser : IDisposable
public sealed class Lease
{
private readonly SemaphoreSlim _sem;
private bool _disposed;
public Releaser(SemaphoreSlim sem)
internal Lease(SemaphoreSlim sem)
{
_sem = sem;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_sem.Release();
_disposed = true;
}
}
}
+6 -13
View File
@@ -6,14 +6,14 @@ public static class GpuWorkLimiter
private static readonly object _lock = new();
private static int _currentLimit = 1;
public static IDisposable Acquire()
public static Lease Acquire()
{
// TODO: Replace the per-call Releaser allocation with the pooled struct-based guard from the
// TODO: Replace the per-call Lease allocation with the pooled struct-based guard from the
// GpuLimiterThroughputBenchmarks so limiter acquisition does not allocate when we enter the
// GPU scanning hot path.
var sem = _semaphore;
sem.Wait();
return new Releaser(sem);
return new Lease(sem);
}
public static void SetLimit(int value)
@@ -32,7 +32,7 @@ public static class GpuWorkLimiter
// Swap to a new semaphore for the new limit. We intentionally
// do not dispose the old semaphore to avoid releasing handles
// that may still be in use by existing Releaser instances.
// that may still be in use by existing Lease instances.
// TODO: Consolidate with GpuPrimeWorkLimiter so both limiters share a pooled SemaphoreSlim and avoid rebuilding the
// limiter state whenever limits are adjusted from the CLI.
_semaphore = new SemaphoreSlim(value, value);
@@ -40,25 +40,18 @@ public static class GpuWorkLimiter
}
}
private sealed class Releaser : IDisposable
public sealed class Lease
{
private readonly SemaphoreSlim _sem;
private bool _disposed;
public Releaser(SemaphoreSlim sem)
internal Lease(SemaphoreSlim sem)
{
_sem = sem;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_sem.Release();
_disposed = true;
}
}
}
@@ -220,7 +220,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong stepValue = (ulong)twoP128;
byte step10 = (byte)(stepValue % 10UL);
byte step8 = (byte)(stepValue & 7UL);
byte step5 = (byte)(stepValue % 5UL);
byte step3 = (byte)(stepValue % 3UL);
byte step7 = (byte)(stepValue % 7UL);
byte step11 = (byte)(stepValue % 11UL);
@@ -229,7 +228,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong currentDivisorValue = (ulong)currentDivisor128;
byte remainder10 = (byte)(currentDivisorValue % 10UL);
byte remainder8 = (byte)(currentDivisorValue & 7UL);
byte remainder5 = (byte)(currentDivisorValue % 5UL);
byte remainder3 = (byte)(currentDivisorValue % 3UL);
byte remainder7 = (byte)(currentDivisorValue % 7UL);
byte remainder11 = (byte)(currentDivisorValue % 11UL);
@@ -275,14 +273,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
byte localRemainder10 = remainder10;
byte localRemainder8 = remainder8;
byte localRemainder5 = remainder5;
byte localRemainder3 = remainder3;
byte localRemainder7 = remainder7;
byte localRemainder11 = remainder11;
for (int i = 0; i < chunkCount; i++)
{
bool passesSmallModuli = localRemainder3 != 0 && localRemainder5 != 0 && localRemainder7 != 0 && localRemainder11 != 0;
bool passesSmallModuli = localRemainder3 != 0 && localRemainder7 != 0 && localRemainder11 != 0;
if (passesSmallModuli && (localRemainder8 == 1 || localRemainder8 == 7) && ((decimalMask >> localRemainder10) & 1) != 0)
{
ulong divisorValue = (ulong)nextDivisor128;
@@ -290,21 +287,16 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData);
if (divisorCycle == prime)
{
int targetIndex = admissibleCount;
divisorSpan[targetIndex] = divisorValue;
exponentSpan[targetIndex] = prime;
divisorDataSpan[targetIndex] = new GpuDivisorPartialData(divisorValue);
offsetSpan[targetIndex] = targetIndex;
countSpan[targetIndex] = 1;
cycleSpan[targetIndex] = divisorCycle;
admissibleCount++;
processedCount += (ulong)(i + 1);
lastProcessed = divisorValue;
coveredRange = true;
return true;
}
}
nextDivisor128 += twoP128;
localRemainder10 = AddMod10(localRemainder10, step10);
localRemainder8 = AddMod8(localRemainder8, step8);
localRemainder5 = AddMod5(localRemainder5, step5);
localRemainder3 = AddMod3(localRemainder3, step3);
localRemainder7 = AddMod7(localRemainder7, step7);
localRemainder11 = AddMod11(localRemainder11, step11);
@@ -312,7 +304,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
remainder10 = localRemainder10;
remainder8 = localRemainder8;
remainder5 = localRemainder5;
remainder3 = localRemainder3;
remainder7 = localRemainder7;
remainder11 = localRemainder11;
@@ -380,19 +371,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod5(byte value, byte delta)
{
const int Modulus = 5;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod7(byte value, byte delta)
@@ -490,7 +468,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private MemoryBuffer1D<byte, Stride1D.Dense> _hitBuffer = null!;
private ulong[] _hostBuffer = null!;
private int _capacity;
private bool _disposed;
internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner)
{
@@ -502,7 +479,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
internal void Reset()
{
_disposed = false;
}
private void EnsureExecutionResourcesLocked(int requiredCapacity)
@@ -549,11 +525,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DivisorScanSession));
}
int length = primes.Length;
// EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the guard stays commented out.
// if (length == 0)
@@ -622,12 +593,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
int sentinel = int.MaxValue;
firstHitView.CopyFromCPU(ref sentinel, 1);
var kernel = _kernel;
if (kernel is null)
{
Monitor.Exit(_lease.ExecutionLock);
throw new InvalidOperationException("GPU divisor kernel was not initialized.");
}
var kernel = _kernel!;
kernel(1, divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView);
@@ -640,12 +606,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_owner._sessionPool.Add(this);
}
}
@@ -695,7 +655,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private void ReturnBatchResources(BatchResources resources) => _resourcePool.Add(resources);
private sealed class BatchResources : IDisposable
private sealed class BatchResources
{
private Accelerator? _accelerator;
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense>? _divisorDataBuffer;
@@ -47,88 +47,83 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
int currentSize;
int i;
UInt128 q = UInt128.Zero;
try
while (kStart <= maxK && Volatile.Read(ref isPrime))
{
while (kStart <= maxK && Volatile.Read(ref isPrime))
remaining = maxK - kStart + UInt128.One;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
// Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + UInt128.One;
if (_kernelType == GpuKernelType.Pow2Mod)
{
remaining = maxK - kStart + UInt128.One;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
// Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + UInt128.One;
if (_kernelType == GpuKernelType.Pow2Mod)
{
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
pow2Kernel(
stream,
currentSize,
exponent,
twoPGpu,
(GpuUInt128)kStart,
last,
divMul,
kernelArgs,
orderBuffer.View,
smallCyclesView,
primeViews.LastOne,
primeViews.LastSeven,
primeViews.LastOnePow2,
primeViews.LastSevenPow2);
}
else
{
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
incKernel(
stream,
currentSize,
exponent,
twoPGpu,
(GpuUInt128)kStart,
last,
divMul,
q0m10,
q0m8,
q0m3,
q0m5,
orderBuffer.View,
smallCyclesView);
}
stream.Synchronize();
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
if (_kernelType == GpuKernelType.Pow2Mod)
{
for (i = 0; i < currentSize; i++)
{
if (orders[i] != 0UL)
{
Volatile.Write(ref isPrime, false);
break;
}
}
}
else
{
q = twoP * kStart + UInt128.One;
for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP)
{
if (orders[i] != 0UL && q.IsPrimeCandidate())
{
Volatile.Write(ref isPrime, false);
}
}
}
kStart += (UInt128)currentSize;
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
pow2Kernel(
stream,
currentSize,
exponent,
twoPGpu,
(GpuUInt128)kStart,
last,
divMul,
kernelArgs,
orderBuffer.View,
smallCyclesView,
primeViews.LastOne,
primeViews.LastSeven,
primeViews.LastOnePow2,
primeViews.LastSevenPow2);
}
else
{
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
incKernel(
stream,
currentSize,
exponent,
twoPGpu,
(GpuUInt128)kStart,
last,
divMul,
q0m10,
q0m8,
q0m3,
q0m5,
orderBuffer.View,
smallCyclesView);
}
stream.Synchronize();
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
if (_kernelType == GpuKernelType.Pow2Mod)
{
for (i = 0; i < currentSize; i++)
{
if (orders[i] != 0UL)
{
Volatile.Write(ref isPrime, false);
break;
}
}
}
else
{
q = twoP * kStart + UInt128.One;
for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP)
{
if (orders[i] != 0UL && q.IsPrimeCandidate())
{
Volatile.Write(ref isPrime, false);
}
}
}
kStart += (UInt128)currentSize;
}
finally
{
ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose();
}
ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose();
}
}
@@ -152,18 +152,12 @@ public class MersenneNumberLucasLehmerGpuTester
}
GpuUInt128[] buffer = ArrayPool<GpuUInt128>.Shared.Rent(count);
try
ComputeResidues(exponents, buffer.AsSpan(0, count));
for (int i = 0; i < count; i++)
{
ComputeResidues(exponents, buffer.AsSpan(0, count));
for (int i = 0; i < count; i++)
{
results[i] = buffer[i].IsZero;
}
}
finally
{
ArrayPool<GpuUInt128>.Shared.Return(buffer);
results[i] = buffer[i].IsZero;
}
ArrayPool<GpuUInt128>.Shared.Return(buffer);
}
private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu)
@@ -4,19 +4,19 @@ namespace PerfectNumbers.Core.Gpu;
public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuOrder)
{
private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder;
private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder;
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var execution = gpuLease.EnterExecutionScope();
var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
UInt128 kStart = UInt128.One;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var execution = gpuLease.EnterExecutionScope();
var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
UInt128 kStart = UInt128.One;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
var kernel = _kernelType switch
{
@@ -26,48 +26,42 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
try
UInt128 remaining;
int currentSize;
int found = 0;
while (kStart <= maxK && Volatile.Read(ref isPrime))
{
UInt128 remaining;
int currentSize;
int found = 0;
while (kStart <= maxK && Volatile.Read(ref isPrime))
{
remaining = maxK - kStart + 1UL;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
foundBuffer.MemSetToZero();
// Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + 1UL;
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
// MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the
// single snapshot block instead of recalculating mod 10/8/5/3 in every iteration.
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
remaining = maxK - kStart + 1UL;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
foundBuffer.MemSetToZero();
// Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + 1UL;
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
// MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the
// single snapshot block instead of recalculating mod 10/8/5/3 in every iteration.
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
// Ensure device has small cycles table for early rejections in order kernels
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
kernelArgs, foundBuffer.View, smallCyclesView);
// Ensure device has small cycles table for early rejections in order kernels
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
kernelArgs, foundBuffer.View, smallCyclesView);
stream.Synchronize();
foundBuffer.View.CopyToCPU(ref found, 1);
if (found != 0)
{
Volatile.Write(ref isPrime, false);
break;
}
stream.Synchronize();
foundBuffer.View.CopyToCPU(ref found, 1);
if (found != 0)
{
Volatile.Write(ref isPrime, false);
break;
}
kStart += (UInt128)currentSize;
}
kStart += (UInt128)currentSize;
}
}
finally
{
foundBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose();
}
}
foundBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose();
}
}
@@ -161,7 +161,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
private void ReturnResources(Accelerator accelerator, ResidueResources resources) =>
_resourcePools.GetOrAdd(accelerator, static _ => new ConcurrentBag<ResidueResources>()).Add(resources);
private sealed class ResidueResources : IDisposable
private sealed class ResidueResources
{
internal ResidueResources(Accelerator accelerator, int capacity)
{
+1 -1
View File
@@ -47,7 +47,7 @@ public static class NttGpuMath
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int>> BitReverseKernelCache = new(); // TODO: Precompute bit-reversal kernels during boot so we can remove the concurrent lookup overhead flagged in the benchmarks.
private sealed class SquareCacheEntry : IDisposable
private sealed class SquareCacheEntry
{
public MemoryBuffer1D<GpuUInt128, Stride1D.Dense> Buffer { get; }
public GpuUInt128 Root { get; }