diff --git a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs index 2318f6d..4d4f6ee 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Runtime.CompilerServices; using ILGPU; using ILGPU.Runtime; @@ -5,16 +6,38 @@ using static PerfectNumbers.Core.Gpu.GpuContextPool; namespace PerfectNumbers.Core.Gpu; -public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels) +public sealed class GpuKernelLease : IDisposable { - private bool disposedValue; - private IDisposable? _limiter = limiter; - private GpuContextLease? _gpu = gpu; - private KernelContainer _kernels = kernels; - private AcceleratorStream? _stream; - private object? _executionLock = gpu.ExecutionLock; + private static readonly ConcurrentQueue Pool = new(); - public readonly Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released"); + private IDisposable? _limiter; + 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) + { + if (!Pool.TryDequeue(out var lease)) + { + lease = new GpuKernelLease(); + } + + lease._limiter = limiter; + lease._gpu = gpu; + lease._kernels = kernels; + lease._stream = null; + lease._executionLock = gpu.ExecutionLock; + lease._disposed = false; + return lease; + } + + public Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released"); public AcceleratorStream Stream { @@ -32,16 +55,16 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne } } - public readonly ExecutionScope EnterExecutionScope() => new ExecutionScope(_executionLock); + public ExecutionScope EnterExecutionScope() => new ExecutionScope(_executionLock); - public readonly Action, ArrayView> OrderKernel + public Action, ArrayView> OrderKernel { get { var accel = Accelerator; // avoid capturing 'this' in lambda lock (_gpu!.Value.KernelInitLock) { - return KernelContainer.InitOnce(ref _kernels.Order, () => + return KernelContainer.InitOnce(ref _kernels!.Order, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView>(OrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -51,14 +74,14 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne } } - public readonly Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> Pow2ModKernel + public Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> Pow2ModKernel { get { var accel = Accelerator; lock (_gpu!.Value.KernelInitLock) { - return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () => + return KernelContainer.InitOnce(ref _kernels!.Pow2Mod, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(Pow2ModKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -68,14 +91,14 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne } } - public readonly Action, ArrayView1D> IncrementalKernel + public Action, ArrayView1D> IncrementalKernel { get { var accel = Accelerator; lock (_gpu!.Value.KernelInitLock) { - return KernelContainer.InitOnce(ref _kernels.Incremental, () => + return KernelContainer.InitOnce(ref _kernels!.Incremental, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -85,14 +108,14 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne } } - public readonly Action, ArrayView1D> IncrementalOrderKernel + public Action, ArrayView1D> IncrementalOrderKernel { get { var accel = Accelerator; lock (_gpu!.Value.KernelInitLock) { - return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () => + return KernelContainer.InitOnce(ref _kernels!.IncrementalOrder, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -102,14 +125,14 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne } } - public readonly Action, ArrayView1D> Pow2ModOrderKernel + public Action, ArrayView1D> Pow2ModOrderKernel { get { var accel = Accelerator; lock (_gpu!.Value.KernelInitLock) { - return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () => + return KernelContainer.InitOnce(ref _kernels!.Pow2ModOrder, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(Pow2ModOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -119,14 +142,14 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne } } - public readonly Action, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> SmallPrimeFactorKernel + public Action, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> SmallPrimeFactorKernel { get { var accel = Accelerator; lock (_gpu!.Value.KernelInitLock) { - return KernelContainer.InitOnce(ref _kernels.SmallPrimeFactor, () => + return KernelContainer.InitOnce(ref _kernels!.SmallPrimeFactor, () => { var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D, int, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(SmallPrimeFactorKernelScan); var kernel = KernelUtil.GetKernel(loaded); @@ -343,26 +366,28 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne private void Dispose(bool disposing) { - if (!disposedValue) + if (_disposed) { - if (disposing) - { - _stream?.Dispose(); - _stream = null; - - _gpu?.Dispose(); - _gpu = null; - - _executionLock = null; - - _limiter?.Dispose(); - _limiter = null; - } - - // free unmanaged resources (unmanaged objects) and override finalizer - // set large fields to null - disposedValue = true; + return; } + + if (disposing) + { + _stream?.Dispose(); + _stream = null; + + _gpu?.Dispose(); + _gpu = null; + + _executionLock = null; + + _limiter?.Dispose(); + _limiter = null; + } + + _kernels = null; + _disposed = true; + Pool.Enqueue(this); } private static void Pow2ModKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _, @@ -677,10 +702,8 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne public void Dispose() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); - // Enabled this when the struct is converted to a class - // GC.SuppressFinalize(this); + System.GC.SuppressFinalize(this); } public readonly struct ExecutionScope diff --git a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs index 2e63cd0..c16d9ca 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -283,7 +283,7 @@ public class GpuKernelPool var gpu = RentPreferred(preferCpu: !useGpuOrder); var accelerator = gpu.Accelerator; var kernels = GetKernels(accelerator); - return new(limiter, gpu, kernels); + return GpuKernelLease.Rent(limiter, gpu, kernels); } ///