Merge pull request #253 from mlemanczyk/codex/optimize-evenperfectbitscanner-for-primetestergpucontextleas

Add GPU lease warm-up and shared limiter
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-11-02 14:02:42 +01:00
commit 507daaddb9
4 files changed
+98 -58

No files matched your search

+3 -5
View File
@@ -58,14 +58,15 @@ internal static class Program
bool startPrimeProvided = _cliArguments.StartPrimeProvided; bool startPrimeProvided = _cliArguments.StartPrimeProvided;
int threadCount = Math.Max(1, _cliArguments.ThreadCount); int threadCount = Math.Max(1, _cliArguments.ThreadCount);
int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch); int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch);
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
UnboundedTaskScheduler.ConfigureThreadCount(threadCount); UnboundedTaskScheduler.ConfigureThreadCount(threadCount);
PrimeTester.GpuBatchSize = gpuPrimeBatch; PrimeTester.GpuBatchSize = gpuPrimeBatch;
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
GpuContextPool.WarmUpPool(threadCount); GpuContextPool.WarmUpPool(threadCount);
PrimeTester.WarmUpGpuKernels(threadCount); PrimeTester.WarmUpGpuKernels(gpuPrimeThreads);
Console.WriteLine("Starting up threads..."); Console.WriteLine("Starting up threads...");
_ = UnboundedTaskScheduler.Instance; _ = UnboundedTaskScheduler.Instance;
int blockSize = Math.Max(1, _cliArguments.BlockSize); int blockSize = Math.Max(1, _cliArguments.BlockSize);
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
GpuKernelType kernelType = _cliArguments.KernelType; GpuKernelType kernelType = _cliArguments.KernelType;
bool useBitTransform = _cliArguments.UseBitTransform; bool useBitTransform = _cliArguments.UseBitTransform;
bool useOrder = _cliArguments.UseOrder; bool useOrder = _cliArguments.UseOrder;
@@ -117,9 +118,6 @@ internal static class Program
return; return;
} }
// Apply GPU prime sieve runtime configuration
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
PrimeTester.GpuBatchSize = gpuPrimeBatch;
MersenneDivisorCycles mersenneDivisorCycles = new(); MersenneDivisorCycles mersenneDivisorCycles = new();
if (!File.Exists(cyclesPath) || continueCyclesGeneration) if (!File.Exists(cyclesPath) || continueCyclesGeneration)
+17 -7
View File
@@ -48,7 +48,7 @@ public static class GpuContextPool
private static int WarmedGpuContextCount; private static int WarmedGpuContextCount;
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu. // Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
public static GpuContextLease Rent() public static GpuContextLease Rent()
{ {
if (GpuPool.TryDequeue(out var gpu)) if (GpuPool.TryDequeue(out var gpu))
{ {
@@ -59,6 +59,7 @@ public static class GpuContextPool
return new GpuContextLease(new PooledContext()); return new GpuContextLease(new PooledContext());
} }
public static void WarmUpPool(int threadCount) public static void WarmUpPool(int threadCount)
{ {
if (threadCount <= WarmedGpuContextCount) if (threadCount <= WarmedGpuContextCount)
@@ -98,21 +99,30 @@ public static class GpuContextPool
GpuPool.Enqueue(ctx); GpuPool.Enqueue(ctx);
} }
public readonly struct GpuContextLease public struct GpuContextLease
{ {
private readonly PooledContext _ctx; private PooledContext? _ctx;
private bool _disposed;
internal GpuContextLease(PooledContext ctx) internal GpuContextLease(PooledContext ctx)
{ {
_ctx = ctx; _ctx = ctx;
_disposed = false;
} }
public readonly Context Context => _ctx.Context; public Context Context => _ctx!.Context;
public readonly Accelerator Accelerator => _ctx.Accelerator; public Accelerator Accelerator => _ctx!.Accelerator;
public readonly void Dispose() public void Dispose()
{ {
Return(_ctx); if (_disposed)
{
return;
}
_disposed = true;
Return(_ctx!);
_ctx = null;
} }
} }
} }
+21 -12
View File
@@ -1,3 +1,6 @@
using System;
using System.Threading;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public static class GpuPrimeWorkLimiter public static class GpuPrimeWorkLimiter
@@ -10,24 +13,31 @@ public static class GpuPrimeWorkLimiter
// TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve // TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
// GPU jobs stop allocating Lease instances on every acquisition. // GPU jobs stop allocating Lease instances on every acquisition.
var sem = _semaphore; var sem = _semaphore;
// sem.Wait(); sem.Wait();
return new Lease(sem); return new Lease(sem);
} }
public static void SetLimit(int value) public static void SetLimit(int value)
{ {
if (value == _currentLimit) if (value <= 0)
{ {
return; value = 1;
} }
// TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using if (value == _currentLimit)
// the same semaphore pool without reallocating per adjustment. {
_semaphore = new SemaphoreSlim(value, value); return;
_currentLimit = value; }
// TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using
// the same semaphore pool without reallocating per adjustment.
var previous = _semaphore;
_semaphore = new SemaphoreSlim(value, value);
_currentLimit = value;
previous.Dispose();
} }
public sealed class Lease public sealed class Lease : IDisposable
{ {
private readonly SemaphoreSlim _sem; private readonly SemaphoreSlim _sem;
@@ -38,8 +48,7 @@ public static class GpuPrimeWorkLimiter
public void Dispose() public void Dispose()
{ {
// _sem.Release(); _sem.Release();
} }
} }
} }
+57 -34
View File
@@ -159,18 +159,7 @@ public sealed class PrimeTester
return; return;
} }
int toWarm = target - WarmedGpuLeaseCount; PrimeTesterGpuContextPool.WarmUp(target, GpuBatchSize);
var leases = new PrimeTesterGpuContextPool.PrimeTesterGpuContextLease[toWarm];
for (int i = 0; i < toWarm; i++)
{
leases[i] = PrimeTesterGpuContextPool.Rent(GpuBatchSize);
}
for (int i = 0; i < toWarm; i++)
{
leases[i].Dispose();
}
WarmedGpuLeaseCount = target; WarmedGpuLeaseCount = target;
} }
} }
@@ -229,6 +218,8 @@ public sealed class PrimeTester
internal static class PrimeTesterGpuContextPool internal static class PrimeTesterGpuContextPool
{ {
private static readonly ConcurrentQueue<PrimeTesterGpuContextLease> Pool = new(); private static readonly ConcurrentQueue<PrimeTesterGpuContextLease> Pool = new();
private static readonly object WarmUpLock = new();
private static int WarmedLeaseCount;
private static readonly object SharedTablesLock = new(); private static readonly object SharedTablesLock = new();
private static readonly Dictionary<string, SharedHeuristicGpuTables> SharedTables = new(); private static readonly Dictionary<string, SharedHeuristicGpuTables> SharedTables = new();
@@ -267,21 +258,46 @@ public sealed class PrimeTester
} }
} }
internal static PrimeTesterGpuContextLease Rent(int minBufferCapacity = 1) internal static void WarmUp(int target, int minBufferCapacity)
{ {
if (!Pool.TryDequeue(out var lease)) if (target <= 0)
{ {
lease = new PrimeTesterGpuContextLease(minBufferCapacity); return;
}
else
{
lease.EnsureCapacity(minBufferCapacity);
} }
lease.ResetReturnFlag(); lock (WarmUpLock)
return lease; {
if (target <= WarmedLeaseCount)
{
return;
}
int toCreate = target - WarmedLeaseCount;
for (int i = 0; i < toCreate; i++)
{
Pool.Enqueue(new PrimeTesterGpuContextLease(minBufferCapacity));
}
WarmedLeaseCount = target;
}
} }
internal static PrimeTesterGpuContextLease Rent(int minBufferCapacity = 1)
{
if (!Pool.TryDequeue(out var lease))
{
lease = new PrimeTesterGpuContextLease(minBufferCapacity);
}
else
{
lease.EnsureCapacity(minBufferCapacity);
}
lease.ResetReturnFlag();
return lease;
}
internal static void Return(PrimeTesterGpuContextLease lease) internal static void Return(PrimeTesterGpuContextLease lease)
{ {
lease.Accelerator.Synchronize(); lease.Accelerator.Synchronize();
@@ -304,6 +320,8 @@ public sealed class PrimeTester
SharedTables.Clear(); SharedTables.Clear();
} }
WarmedLeaseCount = 0;
} }
internal static void Clear(Accelerator accelerator) internal static void Clear(Accelerator accelerator)
@@ -313,23 +331,28 @@ public sealed class PrimeTester
return; return;
} }
var retained = new List<PrimeTesterGpuContextLease>(); lock (WarmUpLock)
while (Pool.TryDequeue(out var lease))
{ {
if (lease.Accelerator == accelerator) var retained = new List<PrimeTesterGpuContextLease>();
while (Pool.TryDequeue(out var lease))
{ {
lease.DisposeResources(); if (lease.Accelerator == accelerator)
{
lease.DisposeResources();
}
else
{
retained.Add(lease);
}
} }
else
{
retained.Add(lease);
}
}
int retainedCount = retained.Count; int retainedCount = retained.Count;
for (int i = 0; i < retainedCount; i++) for (int i = 0; i < retainedCount; i++)
{ {
Pool.Enqueue(retained[i]); Pool.Enqueue(retained[i]);
}
WarmedLeaseCount = Pool.Count;
} }
} }