From abe695788b050fd2cb7ce60c189cc462a5194918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 20 Sep 2025 18:19:21 +0200 Subject: [PATCH] Serialize GPU leases with per-accelerator locks --- PerfectNumbers.Core/Gpu/GpuContextPool.cs | 31 +-- PerfectNumbers.Core/Gpu/GpuKernelPool.cs | 227 ++++++++++++------ .../Gpu/MersenneNumberIncrementalGpuTester.cs | 17 +- .../Gpu/MersenneNumberOrderGpuTester.cs | 78 +++--- .../Gpu/MersenneNumberResidueGpuTester.cs | 15 +- PerfectNumbers.Core/MersenneNumberTester.cs | 98 ++++---- 6 files changed, 284 insertions(+), 182 deletions(-) diff --git a/PerfectNumbers.Core/Gpu/GpuContextPool.cs b/PerfectNumbers.Core/Gpu/GpuContextPool.cs index e7a8e2f..208d148 100644 --- a/PerfectNumbers.Core/Gpu/GpuContextPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuContextPool.cs @@ -10,13 +10,14 @@ public static class GpuContextPool // Default device preference for generic GPU kernels (prime scans, NTT, etc.) public static bool ForceCpu { get; set; } = false; - internal sealed class PooledContext - { - private bool disposed; + internal sealed class PooledContext + { + private bool disposed; - public Context Context { get; } - public Accelerator Accelerator { get; } - public bool IsCpu { get; } + public Context Context { get; } + public Accelerator Accelerator { get; } + public bool IsCpu { get; } + public object ExecutionLock { get; } = new(); public PooledContext(bool preferCpu) { @@ -126,24 +127,26 @@ public static class GpuContextPool public struct GpuContextLease : IDisposable { private readonly PooledContext _ctx; - private bool disposed; + private bool disposed; - internal GpuContextLease(PooledContext ctx, object kernelInitLock) + internal GpuContextLease(PooledContext ctx, object kernelInitLock) { _ctx = ctx; - KernelInitLock = kernelInitLock; - } + KernelInitLock = kernelInitLock; + } public Context Context => _ctx.Context; public Accelerator Accelerator => _ctx.Accelerator; - public object KernelInitLock { get; } + public object KernelInitLock { get; } + + public object ExecutionLock => _ctx.ExecutionLock; - public void Dispose() - { - if (disposed) + public void Dispose() + { + if (disposed) { return; } diff --git a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs index 9724244..9082f4d 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Runtime.CompilerServices; +using System.Threading; using ILGPU; using ILGPU.Runtime; using static PerfectNumbers.Core.Gpu.GpuContextPool; @@ -26,16 +27,16 @@ public readonly struct ResidueAutomatonArgs public sealed class KernelContainer { // Serializes first-time initialization of kernels/buffers per accelerator. - public Action, ArrayView>? Order; - public Action, ArrayView>? Order; + public Action, ArrayView1D>? Incremental; - public Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>? Pow2Mod; - public Action, ArrayView1D>? IncrementalOrder; - public Action, ArrayView1D>? IncrementalOrder; + public Action, ArrayView1D>? Pow2ModOrder; // Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length. @@ -77,72 +78,119 @@ public readonly struct ResiduePrimeViews( public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels) { - private bool disposedValue; - private IDisposable? _limiter = limiter; - private GpuContextLease? _gpu = gpu; - private KernelContainer _kernels = kernels; + private bool disposedValue; + private IDisposable? _limiter = limiter; + private GpuContextLease? _gpu = gpu; + private KernelContainer _kernels = kernels; + private AcceleratorStream? _stream; + private object? _executionLock = gpu.ExecutionLock; - public readonly Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released"); + public readonly Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released"); - public readonly Action, ArrayView> OrderKernel - { - get - { - var accel = Accelerator; // avoid capturing 'this' in lambda - lock (_gpu!.Value.KernelInitLock) - { - return KernelContainer.InitOnce(ref _kernels.Order, () => accel.LoadAutoGroupedStreamKernel, ArrayView>(OrderKernelScan)); - } - } - } + public AcceleratorStream Stream + { + get + { + if (_stream is { } stream) + { + return stream; + } - public readonly Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> Pow2ModKernel + stream = Accelerator.CreateStream(); + _stream = stream; + return stream; + } + } + + public readonly ExecutionScope EnterExecutionScope() + { + return new ExecutionScope(_executionLock); + } + + public readonly Action, ArrayView> OrderKernel + { + get + { + var accel = Accelerator; // avoid capturing 'this' in lambda + lock (_gpu!.Value.KernelInitLock) + { + return KernelContainer.InitOnce(ref _kernels.Order, () => + { + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView>(OrderKernelScan); + var kernel = KernelUtil.GetKernel(loaded); + return kernel.CreateLauncherDelegate, ArrayView>>(); + }); + } + } + } + + public readonly Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> Pow2ModKernel { get { var accel = Accelerator; lock (_gpu!.Value.KernelInitLock) { - return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () => accel.LoadAutoGroupedStreamKernel, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(Pow2ModKernelScan)); + return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () => + { + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(Pow2ModKernelScan); + var kernel = KernelUtil.GetKernel(loaded); + return kernel.CreateLauncherDelegate, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>>(); + }); } } } - public readonly Action, ArrayView1D> IncrementalKernel - { - get - { - var accel = Accelerator; - lock (_gpu!.Value.KernelInitLock) - { - return KernelContainer.InitOnce(ref _kernels.Incremental, () => accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernelScan)); - } - } - } + public readonly Action, ArrayView1D> IncrementalKernel + { + get + { + var accel = Accelerator; + lock (_gpu!.Value.KernelInitLock) + { + return KernelContainer.InitOnce(ref _kernels.Incremental, () => + { + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernelScan); + var kernel = KernelUtil.GetKernel(loaded); + return kernel.CreateLauncherDelegate, ArrayView1D>>(); + }); + } + } + } - public readonly Action, ArrayView1D> IncrementalOrderKernel - { - get - { - var accel = Accelerator; - lock (_gpu!.Value.KernelInitLock) - { - return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () => accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalOrderKernelScan)); - } - } - } + public readonly Action, ArrayView1D> IncrementalOrderKernel + { + get + { + var accel = Accelerator; + lock (_gpu!.Value.KernelInitLock) + { + return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () => + { + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalOrderKernelScan); + var kernel = KernelUtil.GetKernel(loaded); + return kernel.CreateLauncherDelegate, ArrayView1D>>(); + }); + } + } + } - public readonly Action, ArrayView1D> Pow2ModOrderKernel - { - get - { - var accel = Accelerator; - lock (_gpu!.Value.KernelInitLock) - { - return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () => accel.LoadAutoGroupedStreamKernel, ArrayView1D>(Pow2ModOrderKernelScan)); - } - } - } + public readonly Action, ArrayView1D> Pow2ModOrderKernel + { + get + { + var accel = Accelerator; + lock (_gpu!.Value.KernelInitLock) + { + return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () => + { + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(Pow2ModOrderKernelScan); + var kernel = KernelUtil.GetKernel(loaded); + return kernel.CreateLauncherDelegate, ArrayView1D>>(); + }); + } + } + } // TODO: Plumb the small-cycles device buffer into all kernels that can benefit // (some already accept it). Consider a compact type (byte/ushort) for memory footprint. @@ -273,18 +321,23 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne orders[index] = exponent; } - private void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - _gpu?.Dispose(); - _gpu = null; + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _stream?.Dispose(); + _stream = null; - _limiter?.Dispose(); - _limiter = null; - } + _gpu?.Dispose(); + _gpu = null; + + _executionLock = null; + + _limiter?.Dispose(); + _limiter = null; + } // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null @@ -563,13 +616,35 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne Atomic.Or(ref found[0], 1); } - 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); - } + 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); + } + + public readonly struct ExecutionScope : IDisposable + { + private readonly object? _lock; + + public ExecutionScope(object? sync) + { + _lock = sync; + if (_lock is not null) + { + Monitor.Enter(_lock); + } + } + + public void Dispose() + { + if (_lock is not null) + { + Monitor.Exit(_lock); + } + } + } } public class GpuKernelPool @@ -667,10 +742,12 @@ public class GpuKernelPool public static void Run(Action action) { var lease = GetKernel(useGpuOrder: true); + var execution = lease.EnterExecutionScope(); var accelerator = lease.Accelerator; var stream = accelerator.CreateStream(); action(accelerator, stream); stream.Dispose(); + execution.Dispose(); lease.Dispose(); } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs index 8dd0352..6058b26 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs @@ -13,7 +13,9 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) { var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); + var execution = gpuLease.EnterExecutionScope(); var accelerator = gpuLease.Accelerator; + var stream = gpuLease.Stream; int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy UInt128 kStart = 1UL; ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; @@ -54,17 +56,18 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u { q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); var ra = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); - pow2Kernel(currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul, - ra, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven, primeViews.LastOnePow2, primeViews.LastSevenPow2); + pow2Kernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul, + ra, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven, + primeViews.LastOnePow2, primeViews.LastSevenPow2); } else { q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); - incKernel(currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul, + incKernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul, q0m10, q0m8, q0m3, q0m5, orderBuffer.View, smallCyclesView); } - accelerator.Synchronize(); + stream.Synchronize(); orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize); if (_kernelType == GpuKernelType.Pow2Mod) { @@ -95,10 +98,10 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u finally { ArrayPool.Shared.Return(orderArray); + orderBuffer.Dispose(); + execution.Dispose(); + gpuLease.Dispose(); } - orderBuffer.Dispose(); - gpuLease.Dispose(); - } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs index fa11100..d0eda4e 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs @@ -10,7 +10,9 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime) { var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); + var execution = gpuLease.EnterExecutionScope(); var accelerator = gpuLease.Accelerator; + var stream = gpuLease.Stream; int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR UInt128 kStart = UInt128.One; ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; @@ -23,45 +25,51 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO }; var foundBuffer = accelerator.Allocate1D(1); - UInt128 remaining; - int currentSize; - int found = 0; - while (kStart <= maxK && Volatile.Read(ref isPrime)) + try { - remaining = maxK - kStart + 1UL; - currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; - foundBuffer.MemSetToZero(); - // Precompute residue automaton bases for this batch - UInt128 q0 = twoP * kStart + 1UL; - ulong q0m10 = q0.Mod10(); - ulong q0m8 = q0.Mod8(); - ulong q0m3 = q0.Mod3(); - ulong q0m5 = q0.Mod5(); - ulong step10 = ((exponent % 10UL) << 1) % 10UL; - ulong step8 = ((exponent & 7UL) << 1) & 7UL; - ulong step3 = ((exponent % 3UL) << 1) % 3UL; - ulong step5 = ((exponent % 5UL) << 1) % 5UL; - var ra = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); - - - // Ensure device has small cycles table for early rejections in order kernels - var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); - kernel(currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul, - ra, foundBuffer.View, smallCyclesView); - - accelerator.Synchronize(); - foundBuffer.View.CopyToCPU(ref found, 1); - if (found != 0) + UInt128 remaining; + int currentSize; + int found = 0; + while (kStart <= maxK && Volatile.Read(ref isPrime)) { - Volatile.Write(ref isPrime, false); - break; + remaining = maxK - kStart + 1UL; + currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; + foundBuffer.MemSetToZero(); + // Precompute residue automaton bases for this batch + UInt128 q0 = twoP * kStart + 1UL; + ulong q0m10 = q0.Mod10(); + ulong q0m8 = q0.Mod8(); + ulong q0m3 = q0.Mod3(); + ulong q0m5 = q0.Mod5(); + ulong step10 = ((exponent % 10UL) << 1) % 10UL; + ulong step8 = ((exponent & 7UL) << 1) & 7UL; + ulong step3 = ((exponent % 3UL) << 1) % 3UL; + ulong step5 = ((exponent % 5UL) << 1) % 5UL; + var ra = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); + + + // Ensure device has small cycles table for early rejections in order kernels + var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); + kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul, + ra, foundBuffer.View, smallCyclesView); + + stream.Synchronize(); + foundBuffer.View.CopyToCPU(ref found, 1); + if (found != 0) + { + Volatile.Write(ref isPrime, false); + break; + } + + kStart += (UInt128)currentSize; } - - kStart += (UInt128)currentSize; } - - foundBuffer.Dispose(); - gpuLease.Dispose(); + finally + { + foundBuffer.Dispose(); + execution.Dispose(); + gpuLease.Dispose(); + } } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index 2decf3f..2d338a3 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -27,8 +27,10 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) return; } - var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); - var accelerator = gpuLease.Accelerator; + var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); + var execution = gpuLease.EnterExecutionScope(); + var accelerator = gpuLease.Accelerator; + var stream = gpuLease.Stream; // Ensure device has small cycles and primes tables for in-kernel lookup var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); @@ -84,10 +86,10 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) q.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); var ra = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); - kernel(currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, 0UL, + kernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, 0UL, ra, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven, primeViews.LastOnePow2, primeViews.LastSevenPow2); - accelerator.Synchronize(); + stream.Synchronize(); orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize); if (!Volatile.Read(ref isPrime)) { @@ -111,9 +113,10 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) { ArrayPool.Shared.Return(orderArray); orderBuffer.Dispose(); + execution.Dispose(); gpuLease.Dispose(); } - divisorsExhausted = true; - } + divisorsExhausted = true; + } } diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index c46b970..8a30580 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -160,55 +160,63 @@ public sealed class MersenneNumberTester( UInt128[] qs = qsBuffer; - var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); - var accelerator = gpuLease.Accelerator; - var orderKernel = gpuLease.OrderKernel!; + var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); + var execution = gpuLease.EnterExecutionScope(); + var accelerator = gpuLease.Accelerator; + var stream = gpuLease.Stream; + var orderKernel = gpuLease.OrderKernel!; - // Guard long-running kernels by chunking the warm-up across batches. - // Reuse the same ScanBatchSize knob used for scanning. - int batchSize = GpuConstants.ScanBatchSize; - ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; - int offset = 0; - while (offset < idx) - { - int count = Math.Min(batchSize, idx - offset); - var qBuffer = accelerator.Allocate1D(count); - var orderBuffer = accelerator.Allocate1D(count); - // Convert to device-friendly GpuUInt128 on the fly - var tmp = System.Buffers.ArrayPool.Shared.Rent(count); - for (int i = 0; i < count; i++) - { - tmp[i] = (PerfectNumbers.Core.Gpu.GpuUInt128)qs[offset + i]; - } - // Copy only the populated elements to the device buffer to avoid overrun on pooled arrays. - qBuffer.View.CopyFromCPU(ref tmp[0], count); - System.Buffers.ArrayPool.Shared.Return(tmp); - orderKernel(count, exponent, divMul, qBuffer.View, orderBuffer.View); - accelerator.Synchronize(); + try + { + // Guard long-running kernels by chunking the warm-up across batches. + // Reuse the same ScanBatchSize knob used for scanning. + int batchSize = GpuConstants.ScanBatchSize; + ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; + int offset = 0; + while (offset < idx) + { + int count = Math.Min(batchSize, idx - offset); + var qBuffer = accelerator.Allocate1D(count); + var orderBuffer = accelerator.Allocate1D(count); + // Convert to device-friendly GpuUInt128 on the fly + var tmp = System.Buffers.ArrayPool.Shared.Rent(count); + for (int i = 0; i < count; i++) + { + tmp[i] = (PerfectNumbers.Core.Gpu.GpuUInt128)qs[offset + i]; + } + // Copy only the populated elements to the device buffer to avoid overrun on pooled arrays. + qBuffer.View.CopyFromCPU(ref tmp[0], count); + System.Buffers.ArrayPool.Shared.Return(tmp); + orderKernel(stream, count, exponent, divMul, qBuffer.View, orderBuffer.View); + stream.Synchronize(); - ulong[] orders = orderBuffer.GetAsArray1D(); - for (int i = 0; i < count; i++) - { - ulong order = orders[i]; - if (order == 0UL) - { - order = qs[offset + i].CalculateOrder(); - } + ulong[] orders = orderBuffer.GetAsArray1D(); + for (int i = 0; i < count; i++) + { + ulong order = orders[i]; + if (order == 0UL) + { + order = qs[offset + i].CalculateOrder(); + } - if (useOrderCache) - { - OrderCache[qs[offset + i]] = order; - } - } + if (useOrderCache) + { + OrderCache[qs[offset + i]] = order; + } + } - offset += count; - orderBuffer.Dispose(); - qBuffer.Dispose(); - } - - gpuLease.Dispose(); - ArrayPool.Shared.Return(qs); - } + offset += count; + orderBuffer.Dispose(); + qBuffer.Dispose(); + } + } + finally + { + execution.Dispose(); + gpuLease.Dispose(); + ArrayPool.Shared.Return(qs); + } + } public bool IsMersennePrime(ulong exponent) => IsMersennePrime(exponent, out _);