From 964e748498385366b4f4063e30ac2f4219105488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 17 Oct 2025 07:04:11 +0200 Subject: [PATCH 1/2] Pool GPU kernel leases for reuse --- PerfectNumbers.Core/Gpu/GpuKernelLease.cs | 107 +++++++++++++--------- PerfectNumbers.Core/Gpu/GpuKernelPool.cs | 2 +- 2 files changed, 66 insertions(+), 43 deletions(-) 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); } /// From e5d57364b58bd19906bce812cc1a362dbef66c1e Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 17 Oct 2025 08:49:22 +0200 Subject: [PATCH 2/2] GPU limit restored. GpuKernelLease will now return to the pool during Dispose --- PerfectNumbers.Core/Gpu/GpuKernelLease.cs | 9 +++--- .../PrimeOrderCalculator.Cpu.cs | 29 ++++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs index 4d4f6ee..914f026 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs @@ -6,7 +6,7 @@ using static PerfectNumbers.Core.Gpu.GpuContextPool; namespace PerfectNumbers.Core.Gpu; -public sealed class GpuKernelLease : IDisposable +public sealed class GpuKernelLease { private static readonly ConcurrentQueue Pool = new(); @@ -702,8 +702,7 @@ public sealed class GpuKernelLease : IDisposable public void Dispose() { - Dispose(disposing: true); - System.GC.SuppressFinalize(this); + Dispose(disposing: false); } public readonly struct ExecutionScope @@ -715,7 +714,7 @@ public sealed class GpuKernelLease : IDisposable _lock = sync; if (_lock is not null) { - // Monitor.Enter(_lock); + Monitor.Enter(_lock); } } @@ -723,7 +722,7 @@ public sealed class GpuKernelLease : IDisposable { if (_lock is not null) { - // Monitor.Exit(_lock); + Monitor.Exit(_lock); } } } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 41c7626..7a1de6f 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -82,6 +82,7 @@ internal static partial class PrimeOrderCalculator for (int i = 0; i < length; i++) { ulong factor = factorSpan[i].Value; + ulong reduced = phi / factor; if (Pow2EqualsOneCpu(reduced, prime, divisorData)) { @@ -639,16 +640,16 @@ internal static partial class PrimeOrderCalculator private static bool Pow2EqualsOneCpu(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData) { // if (IsGpuPow2Allowed) - // { + { ulong remainder = exponent.Pow2MontgomeryModWindowedGpu(divisorData, false); // GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData); // if (status == GpuPow2ModStatus.Success) // { return remainder == 1UL; // } - // } + } - // return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL; + return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL; } // [ThreadStatic] @@ -744,21 +745,21 @@ internal static partial class PrimeOrderCalculator // int dictionaryCount = counts.Count; // if (dictionaryCount <= FactorSlotCount) // { - int copyIndex = 0; - foreach (KeyValuePair entry in counts) - { - primeSlots[copyIndex] = entry.Key; - exponentSlots[copyIndex] = entry.Value; - copyIndex++; - } + // int copyIndex = 0; + // foreach (KeyValuePair entry in counts) + // { + // primeSlots[copyIndex] = entry.Key; + // exponentSlots[copyIndex] = entry.Value; + // copyIndex++; + // } - factorCount = copyIndex; - ThreadStaticPools.ReturnUlongIntDictionary(counts); - counts = null; + // factorCount = copyIndex; + // ThreadStaticPools.ReturnUlongIntDictionary(counts); + // counts = null; // } // else // { - // useDictionary = true; + useDictionary = true; // factorCount = dictionaryCount; // } }