Various small optimizations

This commit is contained in:
mlemanczyk committed 2025-11-02 11:24:18 +01:00
1 parent 76dfe108a9
commit 07be671726
14 files changed
+206 -276

No files matched your search

+47 -92
View File
@@ -7,23 +7,19 @@ namespace PerfectNumbers.Core.Gpu;
public static class GpuContextPool
{
// The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization
// stays stable; GpuKernelPool JIT-compiles them on first use alongside the small-cycle uploads.
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
public static bool ForceCpu { get; set; } = false;
// The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization
// stays stable; GpuKernelPool JIT-compiles them on first use alongside the small-cycle uploads.
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
internal sealed class PooledContext
{
public Context Context { get; }
public Accelerator Accelerator { get; }
internal sealed class PooledContext
{
public Context Context { get; }
public Accelerator Accelerator { get; }
public bool IsCpu { get; }
public object ExecutionLock { get; } = new();
public PooledContext(bool preferCpu)
public PooledContext()
{
Context = Context.CreateDefault();
Accelerator = Context.GetPreferredDevice(preferCpu).CreateAccelerator(Context);
IsCpu = Accelerator.AcceleratorType == AcceleratorType.CPU;
Accelerator = Context.GetPreferredDevice(false).CreateAccelerator(Context);
DivisorGeneratorGpuCache.WarmUpByDivisorTables(Accelerator);
// NOTE: Avoid loading/compiling any kernel here to prevent implicit
// CL stream/queue creation during accelerator construction.
// Some OpenCL drivers are fragile when a queue is created immediately
@@ -32,7 +28,6 @@ public static class GpuContextPool
public void Dispose()
{
// Ensure all cached GPU buffers for this accelerator are released.
NttGpuMath.ClearCaches(Accelerator);
// Release PrimeTester GPU state for this accelerator (kernel/device primes)
@@ -45,116 +40,76 @@ public static class GpuContextPool
}
}
private static readonly ConcurrentQueue<PooledContext> CpuPool = new();
private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
private static int WarmedGpuContextCount;
public static GpuContextLease Rent()
{
return RentPreferred(ForceCpu);
}
private static int WarmedGpuContextCount;
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
public static GpuContextLease RentPreferred(bool preferCpu)
public static GpuContextLease Rent()
{
if (preferCpu && CpuPool.TryDequeue(out var cpu))
{
return new GpuContextLease(cpu);
}
if (!preferCpu && GpuPool.TryDequeue(out var gpu))
if (GpuPool.TryDequeue(out var gpu))
{
return new GpuContextLease(gpu);
}
// Create a new accelerator when the pool does not have one available.
return new GpuContextLease(new PooledContext(preferCpu));
return new GpuContextLease(new PooledContext());
}
public static void WarmUpPool(int threadCount)
{
if (ForceCpu)
{
return;
}
public static void WarmUpPool(int threadCount)
{
if (threadCount <= WarmedGpuContextCount)
{
return;
}
int target = threadCount >> 2;
if (target == 0)
{
target = threadCount;
}
int toCreate = threadCount - WarmedGpuContextCount;
var contexts = new PooledContext[toCreate];
for (int i = 0; i < toCreate; i++)
{
contexts[i] = new PooledContext();
}
if (target <= WarmedGpuContextCount)
{
return;
}
int toCreate = target - WarmedGpuContextCount;
var contexts = new PooledContext[toCreate];
for (int i = 0; i < toCreate; i++)
{
contexts[i] = new PooledContext(preferCpu: false);
}
for (int i = 0; i < toCreate; i++)
{
GpuPool.Enqueue(contexts[i]);
}
WarmedGpuContextCount = target;
}
for (int i = 0; i < toCreate; i++)
{
GpuPool.Enqueue(contexts[i]);
}
WarmedGpuContextCount = threadCount;
}
public static void DisposeAll()
{
while (CpuPool.TryDequeue(out var cpu))
{
cpu.Dispose();
}
while (GpuPool.TryDequeue(out var gpu))
{
gpu.Dispose();
}
PrimeTester.DisposeGpuContexts();
WarmedGpuContextCount = 0;
WarmedGpuContextCount = 0;
}
private static void Return(PooledContext ctx)
{
ctx.Accelerator.Synchronize();
if (ctx.IsCpu)
{
CpuPool.Enqueue(ctx);
}
else
{
GpuPool.Enqueue(ctx);
}
GpuPool.Enqueue(ctx);
}
public struct GpuContextLease
{
private readonly PooledContext _ctx;
public readonly struct GpuContextLease
{
private readonly PooledContext _ctx;
internal GpuContextLease(PooledContext ctx)
{
_ctx = ctx;
}
internal GpuContextLease(PooledContext ctx)
{
_ctx = ctx;
}
public Context Context => _ctx.Context;
public Accelerator Accelerator => _ctx.Accelerator;
public object ExecutionLock => _ctx.ExecutionLock;
public void Dispose()
{
public readonly Context Context => _ctx.Context;
public readonly Accelerator Accelerator => _ctx.Accelerator;
public readonly void Dispose()
{
Return(_ctx);
}
}
}
}
}
+1 -1
View File
@@ -280,7 +280,7 @@ public class GpuKernelPool
public static GpuKernelLease GetKernel(bool useGpuOrder)
{
var limiter = GpuPrimeWorkLimiter.Acquire();
var gpu = RentPreferred(preferCpu: !useGpuOrder);
var gpu = Rent();
var accelerator = gpu.Accelerator;
var kernels = GetKernels(accelerator);
return GpuKernelLease.Rent(limiter, gpu, kernels);
@@ -492,7 +492,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner)
{
_owner = owner;
_lease = GpuContextPool.RentPreferred(preferCpu: false);
_lease = GpuContextPool.Rent();
_accelerator = _lease.Accelerator;
_capacity = Math.Max(1, owner._gpuBatchSize);
}
@@ -657,7 +657,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
return lease;
}
return GpuContextPool.RentPreferred(preferCpu: false);
return GpuContextPool.Rent();
}
private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease);
@@ -37,7 +37,7 @@ public sealed class MersenneNumberDivisorGpuTester
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
{
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator;
var kernel = GetKernel(accelerator);
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
@@ -38,16 +38,6 @@ public class MersenneNumberLucasLehmerGpuTester
// Configurable LL slice size to keep kernels short. Default 32.
public int SliceSize = 32;
public bool IsMersennePrime(ulong exponent)
{
// Default to global kernel preference for backward compatibility.
bool runOnGpu = !GpuContextPool.ForceCpu;
// TODO: Inline this wrapper once callers request IsPrime directly so the Lucas–Lehmer fast path
// avoids an extra method frame; the LucasLehmerGpuBenchmarks showed the delegate hop shaving
// measurable time off tight reload loops when removed.
return IsPrime(exponent, runOnGpu);
}
public bool IsPrime(ulong exponent, bool runOnGpu)
{
// Early rejections aligned with incremental/order sieves, but safe for small p:
@@ -78,7 +68,7 @@ public class MersenneNumberLucasLehmerGpuTester
}
else
{
var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu);
var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator;
var kernel = GetKernel(accelerator);
var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch.
@@ -110,7 +100,7 @@ public class MersenneNumberLucasLehmerGpuTester
}
}
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator;
var kernel = GetBatchKernel(accelerator);
@@ -162,7 +152,7 @@ public class MersenneNumberLucasLehmerGpuTester
private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu)
{
var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu);
var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator;
var addKernel = GetAddSmallKernel(accelerator);
var subKernel = GetSubSmallKernel(accelerator);
@@ -132,7 +132,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
return lease;
}
return GpuContextPool.RentPreferred(preferCpu: !_useGpuOrder);
return GpuContextPool.Rent();
}
private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease);
+34 -40
View File
@@ -95,49 +95,48 @@ public sealed class PrimeTester
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPrimeGpu(ulong n)
{
bool forceCpu = GpuContextPool.ForceCpu;
bool belowGpuRange = n < 31UL;
// bool belowGpuRange = n < 31UL;
if (forceCpu || belowGpuRange)
{
// if (belowGpuRange)
// {
return IsPrime(n);
}
// }
var limiter = GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTesterGpuContextPool.Rent(1);
var accelerator = gpu.Accelerator;
var kernel = gpu.Kernel;
// var limiter = GpuPrimeWorkLimiter.Acquire();
// var gpu = PrimeTesterGpuContextPool.Rent(1);
// var accelerator = gpu.Accelerator;
// var kernel = gpu.Kernel;
ulong value = n;
byte flag = 0;
// ulong value = n;
// byte flag = 0;
var input = gpu.Input;
var output = gpu.Output;
var inputView = input.View;
var outputView = output.View;
// var input = gpu.Input;
// var output = gpu.Output;
// var inputView = input.View;
// var outputView = output.View;
inputView.CopyFromCPU(ref value, 1);
kernel(
1,
inputView,
gpu.DevicePrimesDefault.View,
gpu.DevicePrimesLastOne.View,
gpu.DevicePrimesLastSeven.View,
gpu.DevicePrimesLastThree.View,
gpu.DevicePrimesLastNine.View,
gpu.DevicePrimesPow2Default.View,
gpu.DevicePrimesPow2LastOne.View,
gpu.DevicePrimesPow2LastSeven.View,
gpu.DevicePrimesPow2LastThree.View,
gpu.DevicePrimesPow2LastNine.View,
outputView);
accelerator.Synchronize();
outputView.CopyToCPU(ref flag, 1);
// inputView.CopyFromCPU(ref value, 1);
// kernel(
// 1,
// inputView,
// gpu.DevicePrimesDefault.View,
// gpu.DevicePrimesLastOne.View,
// gpu.DevicePrimesLastSeven.View,
// gpu.DevicePrimesLastThree.View,
// gpu.DevicePrimesLastNine.View,
// gpu.DevicePrimesPow2Default.View,
// gpu.DevicePrimesPow2LastOne.View,
// gpu.DevicePrimesPow2LastSeven.View,
// gpu.DevicePrimesPow2LastThree.View,
// gpu.DevicePrimesPow2LastNine.View,
// outputView);
// accelerator.Synchronize();
// outputView.CopyToCPU(ref flag, 1);
gpu.Dispose();
limiter.Dispose();
// gpu.Dispose();
// limiter.Dispose();
return flag != 0;
// return flag != 0;
}
public static int GpuBatchSize { get; set; } = 262_144;
@@ -147,11 +146,6 @@ public sealed class PrimeTester
public static void WarmUpGpuKernels(int threadCount)
{
if (GpuContextPool.ForceCpu)
{
return;
}
int target = threadCount >> 2;
if (target == 0)
{