From a4ac82e74d0ad0247981d0dcca7007b1ffb34ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 29 Oct 2025 18:13:51 +0100 Subject: [PATCH] Inline GPU kernel state into prime tester leases --- .../HeuristicCombinedPrimeTester.cs | 36 +- PerfectNumbers.Core/HeuristicPrimeTester.cs | 4 +- PerfectNumbers.Core/PrimeTester.cs | 540 +++++++++--------- 3 files changed, 300 insertions(+), 280 deletions(-) diff --git a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs index 0f59f36..a713bb5 100644 --- a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs @@ -213,29 +213,29 @@ public sealed class HeuristicCombinedPrimeTester ulong[] divisorArray = divisorPool.Rent(batchCapacity); var limiter = GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(batchCapacity); - var accelerator = gpu.Accelerator; - var state = gpu.State; + var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(batchCapacity); + var accelerator = gpu.Accelerator; + var kernel = gpu.HeuristicTrialDivisionKernel; - bool compositeDetected = false; - int count = 0; + bool compositeDetected = false; + int count = 0; - var inputView = gpu.Input.View; - var outputView = gpu.Output.View; + var inputView = gpu.Input.View; + var outputView = gpu.Output.View; - bool ProcessBatch(int length) - { - inputView.CopyFromCPU(ref divisorArray[0], length); - byte compositeFlag = 0; - outputView.CopyFromCPU(ref compositeFlag, 1); - state.HeuristicTrialDivisionKernel(length, inputView, n, outputView); - accelerator.Synchronize(); - outputView.CopyToCPU(ref compositeFlag, 1); + bool ProcessBatch(int length) + { + inputView.CopyFromCPU(ref divisorArray[0], length); + byte compositeFlag = 0; + outputView.CopyFromCPU(ref compositeFlag, 1); + kernel(length, inputView, n, outputView); + accelerator.Synchronize(); + outputView.CopyToCPU(ref compositeFlag, 1); - return compositeFlag != 0; - } + return compositeFlag != 0; + } - ReadOnlySpan combinedDivisors = GetCombinedDivisors(nMod10); + ReadOnlySpan combinedDivisors = GetCombinedDivisors(nMod10); ReadOnlySpan combinedDivisorSquares = GetCombinedDivisorSquares(nMod10); int length = combinedDivisors.Length; diff --git a/PerfectNumbers.Core/HeuristicPrimeTester.cs b/PerfectNumbers.Core/HeuristicPrimeTester.cs index 364bd62..cdaf7e2 100644 --- a/PerfectNumbers.Core/HeuristicPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicPrimeTester.cs @@ -269,7 +269,7 @@ public sealed class HeuristicPrimeTester var limiter = GpuPrimeWorkLimiter.Acquire(); var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(batchCapacity); var accelerator = gpu.Accelerator; - var state = gpu.State; + var kernel = gpu.HeuristicTrialDivisionKernel; bool compositeDetected = false; int count = 0; @@ -282,7 +282,7 @@ public sealed class HeuristicPrimeTester inputView.CopyFromCPU(ref divisorArray[0], length); byte compositeFlag = 0; outputView.CopyFromCPU(ref compositeFlag, 1); - state.HeuristicTrialDivisionKernel(length, inputView, n, outputView); + kernel(length, inputView, n, outputView); accelerator.Synchronize(); outputView.CopyToCPU(ref compositeFlag, 1); diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 44580d0..8d9705a 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using ILGPU; @@ -97,51 +98,51 @@ public sealed class PrimeTester [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsPrimeGpu(ulong n, CancellationToken ct) - { - bool forceCpu = GpuContextPool.ForceCpu; - bool belowGpuRange = n < 31UL; + { + bool forceCpu = GpuContextPool.ForceCpu; + bool belowGpuRange = n < 31UL; - if (forceCpu || belowGpuRange) - { - return IsPrimeCpu(n, ct); - } + if (forceCpu || belowGpuRange) + { + return IsPrimeCpu(n, ct); + } - var limiter = GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeTesterGpuContextPool.Rent(1); - var state = gpu.State; - var accelerator = gpu.Accelerator; + 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 input = gpu.Input; + var output = gpu.Output; - input.View.CopyFromCPU(ref value, 1); - state.Kernel( - 1, - input.View, - state.DevicePrimesDefault.View, - state.DevicePrimesLastOne.View, - state.DevicePrimesLastSeven.View, - state.DevicePrimesLastThree.View, - state.DevicePrimesLastNine.View, - state.DevicePrimesPow2Default.View, - state.DevicePrimesPow2LastOne.View, - state.DevicePrimesPow2LastSeven.View, - state.DevicePrimesPow2LastThree.View, - state.DevicePrimesPow2LastNine.View, - output.View); - accelerator.Synchronize(); - output.View.CopyToCPU(ref flag, 1); + input.View.CopyFromCPU(ref value, 1); + kernel( + 1, + input.View, + 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, + output.View); + accelerator.Synchronize(); + output.View.CopyToCPU(ref flag, 1); - gpu.Dispose(); - limiter.Dispose(); + gpu.Dispose(); + limiter.Dispose(); - return flag != 0; - } + return flag != 0; + } - public static bool IsPrimeCpu(ulong n, CancellationToken ct) +public static bool IsPrimeCpu(ulong n, CancellationToken ct) { // Preserve the legacy entry point for callers that bypass the thread-local caches. return IsPrime(n, ct); @@ -152,276 +153,295 @@ public sealed class PrimeTester [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsPrimeBatchGpu(ReadOnlySpan values, Span results) - { - var limiter = GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeTesterGpuContextPool.Rent(GpuBatchSize); - var state = gpu.State; - var accelerator = gpu.Accelerator; - int totalLength = values.Length; - int batchSize = GpuBatchSize; + { + var limiter = GpuPrimeWorkLimiter.Acquire(); + var gpu = PrimeTesterGpuContextPool.Rent(GpuBatchSize); + var accelerator = gpu.Accelerator; + var kernel = gpu.Kernel; + int totalLength = values.Length; + int batchSize = GpuBatchSize; - var input = gpu.Input; - var output = gpu.Output; - ArrayPool pool = ThreadStaticPools.UlongPool; - ulong[] temp = pool.Rent(batchSize); + var input = gpu.Input; + var output = gpu.Output; + ArrayPool pool = ThreadStaticPools.UlongPool; + ulong[] temp = pool.Rent(batchSize); - int pos = 0; - while (pos < totalLength) - { - int remaining = totalLength - pos; - int count = remaining > batchSize ? batchSize : remaining; + int pos = 0; + while (pos < totalLength) + { + int remaining = totalLength - pos; + int count = remaining > batchSize ? batchSize : remaining; - values.Slice(pos, count).CopyTo(temp); - input.View.CopyFromCPU(ref temp[0], count); + values.Slice(pos, count).CopyTo(temp); + input.View.CopyFromCPU(ref temp[0], count); - state.Kernel( - count, - input.View, - state.DevicePrimesDefault.View, - state.DevicePrimesLastOne.View, - state.DevicePrimesLastSeven.View, - state.DevicePrimesLastThree.View, - state.DevicePrimesLastNine.View, - state.DevicePrimesPow2Default.View, - state.DevicePrimesPow2LastOne.View, - state.DevicePrimesPow2LastSeven.View, - state.DevicePrimesPow2LastThree.View, - state.DevicePrimesPow2LastNine.View, - output.View); - accelerator.Synchronize(); - output.View.CopyToCPU(ref results[pos], count); + kernel( + count, + input.View, + 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, + output.View); + accelerator.Synchronize(); + output.View.CopyToCPU(ref results[pos], count); - pos += count; - } + pos += count; + } - pool.Return(temp, clearArray: false); + pool.Return(temp, clearArray: false); - gpu.Dispose(); - limiter.Dispose(); - } + gpu.Dispose(); + limiter.Dispose(); + } - internal static class PrimeTesterGpuContextPool - { - internal sealed class PooledContext - { - public Context Context { get; } + internal static class PrimeTesterGpuContextPool + { + internal sealed class PooledContext + { + public Context Context { get; } - public Accelerator Accelerator { get; } + public Accelerator Accelerator { get; } - public PooledContext() - { - Context = Context.CreateDefault(); - Accelerator = Context.GetPreferredDevice(false).CreateAccelerator(Context); - } + public Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> Kernel { get; } - public void Dispose() - { - ClearGpuCaches(Accelerator); - Accelerator.Dispose(); - Context.Dispose(); - } - } + public Action, ulong, ArrayView> HeuristicTrialDivisionKernel { get; } - private static readonly ConcurrentQueue Pool = new(); + public MemoryBuffer1D DevicePrimesDefault { get; } - internal static PrimeTesterGpuContextLease Rent(int minBufferCapacity = 1) - { - if (Pool.TryDequeue(out var ctx)) - { - return new PrimeTesterGpuContextLease(ctx, minBufferCapacity); - } + public MemoryBuffer1D DevicePrimesLastOne { get; } - return new PrimeTesterGpuContextLease(new PooledContext(), minBufferCapacity); - } + public MemoryBuffer1D DevicePrimesLastSeven { get; } - internal static void DisposeAll() - { - while (Pool.TryDequeue(out var ctx)) - { - ctx.Dispose(); - } - } + public MemoryBuffer1D DevicePrimesLastThree { get; } - private static void Return(PooledContext ctx) - { - ctx.Accelerator.Synchronize(); - Pool.Enqueue(ctx); - } + public MemoryBuffer1D DevicePrimesLastNine { get; } - internal sealed class PrimeTesterGpuContextLease : IDisposable - { - private readonly PooledContext _ctx; + public MemoryBuffer1D DevicePrimesPow2Default { get; } - public readonly Accelerator Accelerator; + public MemoryBuffer1D DevicePrimesPow2LastOne { get; } - public readonly KernelState State; + public MemoryBuffer1D DevicePrimesPow2LastSeven { get; } - public MemoryBuffer1D Input; + public MemoryBuffer1D DevicePrimesPow2LastThree { get; } - public MemoryBuffer1D Output; + public MemoryBuffer1D DevicePrimesPow2LastNine { get; } - public int BufferCapacity; + public MemoryBuffer1D Input { get; private set; } - internal PrimeTesterGpuContextLease(PooledContext ctx, int minBufferCapacity) - { - // The pool never requests a zero-length buffer, so the old guard stays commented out to document the invariant. - // if (minBufferCapacity < 1) - // { - // minBufferCapacity = 1; - // } + public MemoryBuffer1D Output { get; private set; } - _ctx = ctx; - Accelerator = ctx.Accelerator; - State = GpuKernelState.GetOrCreate(Accelerator); - Input = Accelerator.Allocate1D(minBufferCapacity); - Output = Accelerator.Allocate1D(minBufferCapacity); - BufferCapacity = minBufferCapacity; - } + public int BufferCapacity { get; private set; } - public void EnsureCapacity(int minCapacity) - { - if (minCapacity <= BufferCapacity) - { - return; - } + public PooledContext(int minBufferCapacity) + { + Context = Context.CreateDefault(); + Accelerator = Context.GetPreferredDevice(false).CreateAccelerator(Context); + Kernel = Accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); + HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel, ulong, ArrayView>(PrimeTesterKernels.HeuristicTrialDivisionKernel); - Input.Dispose(); - Output.Dispose(); + var primesDefault = DivisorGenerator.SmallPrimes; + DevicePrimesDefault = Accelerator.Allocate1D(primesDefault.Length); + DevicePrimesDefault.View.CopyFromCPU(primesDefault); - Input = Accelerator.Allocate1D(minCapacity); - Output = Accelerator.Allocate1D(minCapacity); - BufferCapacity = minCapacity; - } + var primesLastOne = DivisorGenerator.SmallPrimesLastOne; + DevicePrimesLastOne = Accelerator.Allocate1D(primesLastOne.Length); + DevicePrimesLastOne.View.CopyFromCPU(primesLastOne); - public void Dispose() - { - Input.Dispose(); - Output.Dispose(); - BufferCapacity = 0; - Return(_ctx); - } - } - } - // Per-accelerator GPU state for prime sieve (kernel + uploaded primes). - internal sealed class KernelState - { - public Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> Kernel; - public Action, ulong, ArrayView> HeuristicTrialDivisionKernel; - public MemoryBuffer1D DevicePrimesDefault; - public MemoryBuffer1D DevicePrimesLastOne; - public MemoryBuffer1D DevicePrimesLastSeven; - public MemoryBuffer1D DevicePrimesLastThree; - public MemoryBuffer1D DevicePrimesLastNine; - public MemoryBuffer1D DevicePrimesPow2Default; - public MemoryBuffer1D DevicePrimesPow2LastOne; - public MemoryBuffer1D DevicePrimesPow2LastSeven; - public MemoryBuffer1D DevicePrimesPow2LastThree; - public MemoryBuffer1D DevicePrimesPow2LastNine; - public bool IsDisposed; + var primesLastSeven = DivisorGenerator.SmallPrimesLastSeven; + DevicePrimesLastSeven = Accelerator.Allocate1D(primesLastSeven.Length); + DevicePrimesLastSeven.View.CopyFromCPU(primesLastSeven); - public KernelState(Accelerator accelerator) - { - IsDisposed = false; - // Compile once per accelerator and upload primes once. - Kernel = accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); - HeuristicTrialDivisionKernel = accelerator.LoadAutoGroupedStreamKernel, ulong, ArrayView>(PrimeTesterKernels.HeuristicTrialDivisionKernel); + var primesLastThree = DivisorGenerator.SmallPrimesLastThree; + DevicePrimesLastThree = Accelerator.Allocate1D(primesLastThree.Length); + DevicePrimesLastThree.View.CopyFromCPU(primesLastThree); - var primesDefault = DivisorGenerator.SmallPrimes; - DevicePrimesDefault = accelerator.Allocate1D(primesDefault.Length); - DevicePrimesDefault.View.CopyFromCPU(primesDefault); + var primesLastNine = DivisorGenerator.SmallPrimesLastNine; + DevicePrimesLastNine = Accelerator.Allocate1D(primesLastNine.Length); + DevicePrimesLastNine.View.CopyFromCPU(primesLastNine); - var primesLastOne = DivisorGenerator.SmallPrimesLastOne; - DevicePrimesLastOne = accelerator.Allocate1D(primesLastOne.Length); - DevicePrimesLastOne.View.CopyFromCPU(primesLastOne); + var primesPow2Default = DivisorGenerator.SmallPrimesPow2; + DevicePrimesPow2Default = Accelerator.Allocate1D(primesPow2Default.Length); + DevicePrimesPow2Default.View.CopyFromCPU(primesPow2Default); - var primesLastSeven = DivisorGenerator.SmallPrimesLastSeven; - DevicePrimesLastSeven = accelerator.Allocate1D(primesLastSeven.Length); - DevicePrimesLastSeven.View.CopyFromCPU(primesLastSeven); + var primesPow2LastOne = DivisorGenerator.SmallPrimesPow2LastOne; + DevicePrimesPow2LastOne = Accelerator.Allocate1D(primesPow2LastOne.Length); + DevicePrimesPow2LastOne.View.CopyFromCPU(primesPow2LastOne); - var primesLastThree = DivisorGenerator.SmallPrimesLastThree; - DevicePrimesLastThree = accelerator.Allocate1D(primesLastThree.Length); - DevicePrimesLastThree.View.CopyFromCPU(primesLastThree); + var primesPow2LastSeven = DivisorGenerator.SmallPrimesPow2LastSeven; + DevicePrimesPow2LastSeven = Accelerator.Allocate1D(primesPow2LastSeven.Length); + DevicePrimesPow2LastSeven.View.CopyFromCPU(primesPow2LastSeven); - var primesLastNine = DivisorGenerator.SmallPrimesLastNine; - DevicePrimesLastNine = accelerator.Allocate1D(primesLastNine.Length); - DevicePrimesLastNine.View.CopyFromCPU(primesLastNine); + var primesPow2LastThree = DivisorGenerator.SmallPrimesPow2LastThree; + DevicePrimesPow2LastThree = Accelerator.Allocate1D(primesPow2LastThree.Length); + DevicePrimesPow2LastThree.View.CopyFromCPU(primesPow2LastThree); - var primesPow2Default = DivisorGenerator.SmallPrimesPow2; - DevicePrimesPow2Default = accelerator.Allocate1D(primesPow2Default.Length); - DevicePrimesPow2Default.View.CopyFromCPU(primesPow2Default); + var primesPow2LastNine = DivisorGenerator.SmallPrimesPow2LastNine; + DevicePrimesPow2LastNine = Accelerator.Allocate1D(primesPow2LastNine.Length); + DevicePrimesPow2LastNine.View.CopyFromCPU(primesPow2LastNine); - var primesPow2LastOne = DivisorGenerator.SmallPrimesPow2LastOne; - DevicePrimesPow2LastOne = accelerator.Allocate1D(primesPow2LastOne.Length); - DevicePrimesPow2LastOne.View.CopyFromCPU(primesPow2LastOne); + BufferCapacity = minBufferCapacity < 1 ? 1 : minBufferCapacity; + Input = Accelerator.Allocate1D(BufferCapacity); + Output = Accelerator.Allocate1D(BufferCapacity); + } - var primesPow2LastSeven = DivisorGenerator.SmallPrimesPow2LastSeven; - DevicePrimesPow2LastSeven = accelerator.Allocate1D(primesPow2LastSeven.Length); - DevicePrimesPow2LastSeven.View.CopyFromCPU(primesPow2LastSeven); + public void EnsureCapacity(int minCapacity) + { + if (minCapacity < 1) + { + minCapacity = 1; + } - var primesPow2LastThree = DivisorGenerator.SmallPrimesPow2LastThree; - DevicePrimesPow2LastThree = accelerator.Allocate1D(primesPow2LastThree.Length); - DevicePrimesPow2LastThree.View.CopyFromCPU(primesPow2LastThree); + if (minCapacity <= BufferCapacity) + { + return; + } - var primesPow2LastNine = DivisorGenerator.SmallPrimesPow2LastNine; - DevicePrimesPow2LastNine = accelerator.Allocate1D(primesPow2LastNine.Length); - DevicePrimesPow2LastNine.View.CopyFromCPU(primesPow2LastNine); - } + Input.Dispose(); + Output.Dispose(); - public void Clear() - { - if (IsDisposed) - { - return; - } + Input = Accelerator.Allocate1D(minCapacity); + Output = Accelerator.Allocate1D(minCapacity); + BufferCapacity = minCapacity; + } - DevicePrimesDefault.Dispose(); - DevicePrimesLastOne.Dispose(); - DevicePrimesLastSeven.Dispose(); - DevicePrimesLastThree.Dispose(); - DevicePrimesLastNine.Dispose(); - DevicePrimesPow2Default.Dispose(); - DevicePrimesPow2LastOne.Dispose(); - DevicePrimesPow2LastSeven.Dispose(); - DevicePrimesPow2LastThree.Dispose(); - DevicePrimesPow2LastNine.Dispose(); - IsDisposed = true; - } - } + public void DisposeResources() + { + Input.Dispose(); + Output.Dispose(); + DevicePrimesDefault.Dispose(); + DevicePrimesLastOne.Dispose(); + DevicePrimesLastSeven.Dispose(); + DevicePrimesLastThree.Dispose(); + DevicePrimesLastNine.Dispose(); + DevicePrimesPow2Default.Dispose(); + DevicePrimesPow2LastOne.Dispose(); + DevicePrimesPow2LastSeven.Dispose(); + DevicePrimesPow2LastThree.Dispose(); + DevicePrimesPow2LastNine.Dispose(); + Accelerator.Dispose(); + Context.Dispose(); + } + } - internal static class GpuKernelState - { - // Map accelerator to cached state; use Lazy to serialize kernel creation - private static readonly System.Collections.Concurrent.ConcurrentDictionary> States = new(); - // TODO: Prewarm this per-accelerator cache during startup (and reuse a simple array keyed by accelerator index) - // once the kernel pool exposes deterministic ordering; the Lazy wrappers showed measurable overhead in the - // GpuModularArithmeticBenchmarks hot path. + private static readonly ConcurrentQueue Pool = new(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static KernelState GetOrCreate(Accelerator accelerator) - { - var lazy = States.GetOrAdd( - accelerator, - acc => new Lazy(() => new KernelState(acc), LazyThreadSafetyMode.ExecutionAndPublication)); - return lazy.Value; - } + internal static PrimeTesterGpuContextLease Rent(int minBufferCapacity = 1) + { + if (!Pool.TryDequeue(out var ctx)) + { + ctx = new PooledContext(minBufferCapacity); + } + else + { + ctx.EnsureCapacity(minBufferCapacity); + } - public static void Clear(Accelerator accelerator) - { - if (States.TryRemove(accelerator, out var lazy)) - { - if (lazy.IsValueCreated) - { - var state = lazy.Value; - state.Clear(); - } - } - } - } + return new PrimeTesterGpuContextLease(ctx); + } - // Expose cache clearing for accelerator disposal coordination + internal static void DisposeAll() + { + while (Pool.TryDequeue(out var ctx)) + { + ctx.DisposeResources(); + } + } + + internal static void Clear(Accelerator accelerator) + { + if (accelerator is null) + { + return; + } + + var retained = new List(); + while (Pool.TryDequeue(out var ctx)) + { + if (ctx.Accelerator == accelerator) + { + ctx.DisposeResources(); + } + else + { + retained.Add(ctx); + } + } + + for (int i = 0; i < retained.Count; i++) + { + Pool.Enqueue(retained[i]); + } + } + + private static void Return(PooledContext ctx) + { + ctx.Accelerator.Synchronize(); + Pool.Enqueue(ctx); + } + + internal sealed class PrimeTesterGpuContextLease : IDisposable + { + private readonly PooledContext _ctx; + + internal PrimeTesterGpuContextLease(PooledContext ctx) + { + _ctx = ctx; + } + + public Accelerator Accelerator => _ctx.Accelerator; + + public Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> Kernel => _ctx.Kernel; + + public Action, ulong, ArrayView> HeuristicTrialDivisionKernel => _ctx.HeuristicTrialDivisionKernel; + + public MemoryBuffer1D Input => _ctx.Input; + + public MemoryBuffer1D Output => _ctx.Output; + + public MemoryBuffer1D DevicePrimesDefault => _ctx.DevicePrimesDefault; + + public MemoryBuffer1D DevicePrimesLastOne => _ctx.DevicePrimesLastOne; + + public MemoryBuffer1D DevicePrimesLastSeven => _ctx.DevicePrimesLastSeven; + + public MemoryBuffer1D DevicePrimesLastThree => _ctx.DevicePrimesLastThree; + + public MemoryBuffer1D DevicePrimesLastNine => _ctx.DevicePrimesLastNine; + + public MemoryBuffer1D DevicePrimesPow2Default => _ctx.DevicePrimesPow2Default; + + public MemoryBuffer1D DevicePrimesPow2LastOne => _ctx.DevicePrimesPow2LastOne; + + public MemoryBuffer1D DevicePrimesPow2LastSeven => _ctx.DevicePrimesPow2LastSeven; + + public MemoryBuffer1D DevicePrimesPow2LastThree => _ctx.DevicePrimesPow2LastThree; + + public MemoryBuffer1D DevicePrimesPow2LastNine => _ctx.DevicePrimesPow2LastNine; + + public int BufferCapacity => _ctx.BufferCapacity; + + public void EnsureCapacity(int minCapacity) + { + _ctx.EnsureCapacity(minCapacity); + } + + public void Dispose() + { + Return(_ctx); + } + } + } + // Expose cache clearing for accelerator disposal coordination public static void ClearGpuCaches(Accelerator accelerator) { - GpuKernelState.Clear(accelerator); + PrimeTesterGpuContextPool.Clear(accelerator); } internal static void DisposeGpuContexts()