From 6027a73839260ac4dc9a2e30c054e86f6c1ce9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 1 Nov 2025 09:22:44 +0100 Subject: [PATCH] Split GPU heuristic tables by scope --- .../Gpu/HeuristicGpuDivisorTables.cs | 131 +-- .../Gpu/Kernels/PrimeTesterKernels.cs | 17 +- .../HeuristicCombinedPrimeTester.cs | 27 +- PerfectNumbers.Core/HeuristicPrimeTester.cs | 30 +- PerfectNumbers.Core/PrimeTester.cs | 753 +++++++++++------- docs/gpu-context-rework-plan.md | 8 + 6 files changed, 574 insertions(+), 392 deletions(-) create mode 100644 docs/gpu-context-rework-plan.md diff --git a/PerfectNumbers.Core/Gpu/HeuristicGpuDivisorTables.cs b/PerfectNumbers.Core/Gpu/HeuristicGpuDivisorTables.cs index d295417..5127079 100644 --- a/PerfectNumbers.Core/Gpu/HeuristicGpuDivisorTables.cs +++ b/PerfectNumbers.Core/Gpu/HeuristicGpuDivisorTables.cs @@ -8,83 +8,21 @@ internal readonly struct HeuristicGpuDivisorTables { public static readonly HeuristicGpuDivisorTables Shared = default; - public readonly ArrayView1D CombinedDivisorsEnding1; - public readonly ArrayView1D CombinedDivisorsEnding3; - public readonly ArrayView1D CombinedDivisorsEnding7; - public readonly ArrayView1D CombinedDivisorsEnding9; - public readonly ArrayView1D CombinedDivisorSquaresEnding1; - public readonly ArrayView1D CombinedDivisorSquaresEnding3; - public readonly ArrayView1D CombinedDivisorSquaresEnding7; - public readonly ArrayView1D CombinedDivisorSquaresEnding9; public readonly ArrayView1D GroupADivisors; - public readonly ArrayView1D GroupADivisorSquares; public readonly ArrayView1D GroupBDivisorsEnding1; - public readonly ArrayView1D GroupBDivisorSquaresEnding1; public readonly ArrayView1D GroupBDivisorsEnding7; - public readonly ArrayView1D GroupBDivisorSquaresEnding7; public readonly ArrayView1D GroupBDivisorsEnding9; - public readonly ArrayView1D GroupBDivisorSquaresEnding9; public HeuristicGpuDivisorTables( - ArrayView1D combinedDivisorsEnding1, - ArrayView1D combinedDivisorsEnding3, - ArrayView1D combinedDivisorsEnding7, - ArrayView1D combinedDivisorsEnding9, - ArrayView1D combinedDivisorSquaresEnding1, - ArrayView1D combinedDivisorSquaresEnding3, - ArrayView1D combinedDivisorSquaresEnding7, - ArrayView1D combinedDivisorSquaresEnding9, ArrayView1D groupADivisors, - ArrayView1D groupADivisorSquares, ArrayView1D groupBDivisorsEnding1, - ArrayView1D groupBDivisorSquaresEnding1, ArrayView1D groupBDivisorsEnding7, - ArrayView1D groupBDivisorSquaresEnding7, - ArrayView1D groupBDivisorsEnding9, - ArrayView1D groupBDivisorSquaresEnding9) + ArrayView1D groupBDivisorsEnding9) { - CombinedDivisorsEnding1 = combinedDivisorsEnding1; - CombinedDivisorsEnding3 = combinedDivisorsEnding3; - CombinedDivisorsEnding7 = combinedDivisorsEnding7; - CombinedDivisorsEnding9 = combinedDivisorsEnding9; - CombinedDivisorSquaresEnding1 = combinedDivisorSquaresEnding1; - CombinedDivisorSquaresEnding3 = combinedDivisorSquaresEnding3; - CombinedDivisorSquaresEnding7 = combinedDivisorSquaresEnding7; - CombinedDivisorSquaresEnding9 = combinedDivisorSquaresEnding9; GroupADivisors = groupADivisors; - GroupADivisorSquares = groupADivisorSquares; GroupBDivisorsEnding1 = groupBDivisorsEnding1; - GroupBDivisorSquaresEnding1 = groupBDivisorSquaresEnding1; GroupBDivisorsEnding7 = groupBDivisorsEnding7; - GroupBDivisorSquaresEnding7 = groupBDivisorSquaresEnding7; GroupBDivisorsEnding9 = groupBDivisorsEnding9; - GroupBDivisorSquaresEnding9 = groupBDivisorSquaresEnding9; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArrayView1D SelectCombined(byte ending) - { - return ending switch - { - 1 => CombinedDivisorsEnding1, - 3 => CombinedDivisorsEnding3, - 7 => CombinedDivisorsEnding7, - 9 => CombinedDivisorsEnding9, - _ => ArrayView1D.Empty, - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArrayView1D SelectCombinedSquares(byte ending) - { - return ending switch - { - 1 => CombinedDivisorSquaresEnding1, - 3 => CombinedDivisorSquaresEnding3, - 7 => CombinedDivisorSquaresEnding7, - 9 => CombinedDivisorSquaresEnding9, - _ => ArrayView1D.Empty, - }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -99,18 +37,6 @@ internal readonly struct HeuristicGpuDivisorTables }; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArrayView1D SelectGroupBSquares(byte ending) - { - return ending switch - { - 1 => GroupBDivisorSquaresEnding1, - 7 => GroupBDivisorSquaresEnding7, - 9 => GroupBDivisorSquaresEnding9, - _ => ArrayView1D.Empty, - }; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArrayView1D SelectDivisors(HeuristicGpuDivisorTableKind kind, byte ending) { @@ -120,21 +46,6 @@ internal readonly struct HeuristicGpuDivisorTables HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorsEnding1, HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorsEnding7, HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorsEnding9, - HeuristicGpuDivisorTableKind.Combined => SelectCombined(ending), - _ => ArrayView1D.Empty, - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ArrayView1D SelectDivisorSquares(HeuristicGpuDivisorTableKind kind, byte ending) - { - return kind switch - { - HeuristicGpuDivisorTableKind.GroupA => GroupADivisorSquares, - HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorSquaresEnding1, - HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorSquaresEnding7, - HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorSquaresEnding9, - HeuristicGpuDivisorTableKind.Combined => SelectCombinedSquares(ending), _ => ArrayView1D.Empty, }; } @@ -144,3 +55,43 @@ internal readonly struct HeuristicGpuDivisorTables Unsafe.AsRef(in Shared) = tables; } } + +internal readonly struct HeuristicGpuCombinedDivisorTables +{ + public static readonly HeuristicGpuCombinedDivisorTables Shared = default; + + public readonly ArrayView1D CombinedDivisorsEnding1; + public readonly ArrayView1D CombinedDivisorsEnding3; + public readonly ArrayView1D CombinedDivisorsEnding7; + public readonly ArrayView1D CombinedDivisorsEnding9; + + public HeuristicGpuCombinedDivisorTables( + ArrayView1D combinedDivisorsEnding1, + ArrayView1D combinedDivisorsEnding3, + ArrayView1D combinedDivisorsEnding7, + ArrayView1D combinedDivisorsEnding9) + { + CombinedDivisorsEnding1 = combinedDivisorsEnding1; + CombinedDivisorsEnding3 = combinedDivisorsEnding3; + CombinedDivisorsEnding7 = combinedDivisorsEnding7; + CombinedDivisorsEnding9 = combinedDivisorsEnding9; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayView1D SelectCombined(byte ending) + { + return ending switch + { + 1 => CombinedDivisorsEnding1, + 3 => CombinedDivisorsEnding3, + 7 => CombinedDivisorsEnding7, + 9 => CombinedDivisorsEnding9, + _ => ArrayView1D.Empty, + }; + } + + internal static void InitializeShared(in HeuristicGpuCombinedDivisorTables tables) + { + Unsafe.AsRef(in Shared) = tables; + } +} diff --git a/PerfectNumbers.Core/Gpu/Kernels/PrimeTesterKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/PrimeTesterKernels.cs index d6842e0..f422f1c 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/PrimeTesterKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/PrimeTesterKernels.cs @@ -82,21 +82,22 @@ internal static class PrimeTesterKernels Index1D index, ArrayView resultFlag, ulong n, - ulong maxDivisorSquare, + ulong maxDivisor, HeuristicGpuDivisorTableKind tableKind, - HeuristicGpuDivisorTables tables) + HeuristicGpuDivisorTables tables, + HeuristicGpuCombinedDivisorTables combinedTables) { byte nMod10 = (byte)(n % 10UL); - ArrayView1D divisors = tables.SelectDivisors(tableKind, nMod10); - ArrayView1D divisorSquares = tables.SelectDivisorSquares(tableKind, nMod10); + ArrayView1D divisors = tableKind == HeuristicGpuDivisorTableKind.Combined + ? combinedTables.SelectCombined(nMod10) + : tables.SelectDivisors(tableKind, nMod10); - int divisorLength = (int)divisorSquares.Length; + int divisorLength = (int)divisors.Length; int threadIndex = index; bool skipThread = threadIndex >= divisorLength; - ulong divisorSquare = skipThread ? 0UL : divisorSquares[threadIndex]; - skipThread = skipThread | (divisorSquare > maxDivisorSquare); - ulong divisor = skipThread ? 1UL : divisors[threadIndex]; + skipThread = skipThread | (divisor > maxDivisor); + ulong remainder = skipThread ? 1UL : n % divisor; bool hasFactor = !skipThread & (remainder == 0UL); if (hasFactor) diff --git a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs index 0413fd9..b95b72c 100644 --- a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs @@ -226,12 +226,13 @@ public sealed class HeuristicCombinedPrimeTester private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) { var limiter = GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1); + var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(nMod10, 1, PrimeTester.PrimeTesterGpuContextPool.HeuristicGpuTableScope.GroupABWithCombined); var accelerator = gpu.Accelerator; var kernel = gpu.HeuristicTrialDivisionKernel; var flagView1D = gpu.HeuristicFlag.View; var flagView = flagView1D.AsContiguous(); + ulong maxDivisor = ComputeMaxDivisorLimit(maxDivisorSquare); bool compositeDetected = false; int divisorLength = GetCombinedDivisors(nMod10).Length; @@ -241,9 +242,10 @@ public sealed class HeuristicCombinedPrimeTester divisorLength, flagView, n, - maxDivisorSquare, + maxDivisor, HeuristicGpuDivisorTableKind.Combined, - gpu.HeuristicGpuTables); + gpu.HeuristicGpuTables, + gpu.HeuristicCombinedTables); accelerator.Synchronize(); flagView1D.CopyToCPU(ref compositeFlag, 1); compositeDetected = compositeFlag != 0; @@ -255,7 +257,24 @@ public sealed class HeuristicCombinedPrimeTester [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool EvaluateWithOpenNumericFallback(ulong n) + + private static ulong ComputeMaxDivisorLimit(ulong maxDivisorSquare) + { + ulong maxDivisor = (ulong)Math.Sqrt(maxDivisorSquare); + + while ((maxDivisor + 1) != 0 && (maxDivisor + 1) <= maxDivisorSquare / (maxDivisor + 1)) + { + maxDivisor++; + } + + while (maxDivisor != 0 && maxDivisor > maxDivisorSquare / maxDivisor) + { + maxDivisor--; + } + + return maxDivisor; + } + private static bool EvaluateWithOpenNumericFallback(ulong n) { return Prime.Numbers.IsPrime(n); } diff --git a/PerfectNumbers.Core/HeuristicPrimeTester.cs b/PerfectNumbers.Core/HeuristicPrimeTester.cs index a60227b..e48724d 100644 --- a/PerfectNumbers.Core/HeuristicPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicPrimeTester.cs @@ -241,12 +241,13 @@ public sealed class HeuristicPrimeTester private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) { var limiter = GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1); + var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(nMod10, 1, PrimeTester.PrimeTesterGpuContextPool.HeuristicGpuTableScope.GroupAB); var accelerator = gpu.Accelerator; var kernel = gpu.HeuristicTrialDivisionKernel; var flagView1D = gpu.HeuristicFlag.View; var flagView = flagView1D.AsContiguous(); + ulong maxDivisor = ComputeMaxDivisorLimit(maxDivisorSquare); bool compositeDetected = false; int compositeFlag = 0; @@ -257,9 +258,10 @@ public sealed class HeuristicPrimeTester groupALength, flagView, n, - maxDivisorSquare, + maxDivisor, HeuristicGpuDivisorTableKind.GroupA, - gpu.HeuristicGpuTables); + gpu.HeuristicGpuTables, + gpu.HeuristicCombinedTables); accelerator.Synchronize(); flagView1D.CopyToCPU(ref compositeFlag, 1); compositeDetected = compositeFlag != 0; @@ -291,9 +293,10 @@ public sealed class HeuristicPrimeTester divisorLength, flagView, n, - maxDivisorSquare, + maxDivisor, tableKind, - gpu.HeuristicGpuTables); + gpu.HeuristicGpuTables, + gpu.HeuristicCombinedTables); accelerator.Synchronize(); flagView1D.CopyToCPU(ref compositeFlag, 1); compositeDetected = compositeFlag != 0; @@ -312,6 +315,23 @@ public sealed class HeuristicPrimeTester { return Prime.Numbers.IsPrime(n); } + + private static ulong ComputeMaxDivisorLimit(ulong maxDivisorSquare) + { + ulong maxDivisor = (ulong)Math.Sqrt(maxDivisorSquare); + + while ((maxDivisor + 1) != 0 && (maxDivisor + 1) <= maxDivisorSquare / (maxDivisor + 1)) + { + maxDivisor++; + } + + while (maxDivisor != 0 && maxDivisor > maxDivisorSquare / maxDivisor) + { + maxDivisor--; + } + + return maxDivisor; + } internal static ulong ComputeHeuristicDivisorSquareLimit(ulong n) { return n; diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 9a8fc90..6568fb0 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -141,7 +141,7 @@ public sealed class PrimeTester public static int GpuBatchSize { get; set; } = 262_144; - private static readonly object GpuWarmUpLock = new(); + private static readonly object GpuWarmUpLock = new(); private static int WarmedGpuLeaseCount; public static void WarmUpGpuKernels(int threadCount) @@ -161,9 +161,17 @@ public sealed class PrimeTester int toWarm = target - WarmedGpuLeaseCount; var leases = new PrimeTesterGpuContextPool.PrimeTesterGpuContextLease[toWarm]; + ReadOnlySpan supportedDigits = PrimeTesterGpuContextPool.SupportedDigits; + int digitIndex = 0; for (int i = 0; i < toWarm; i++) { - leases[i] = PrimeTesterGpuContextPool.Rent(GpuBatchSize); + byte trailingDigit = supportedDigits[digitIndex]; + leases[i] = PrimeTesterGpuContextPool.Rent(trailingDigit, GpuBatchSize); + digitIndex++; + if (digitIndex == supportedDigits.Length) + { + digitIndex = 0; + } } for (int i = 0; i < toWarm; i++) @@ -179,99 +187,133 @@ public sealed class PrimeTester public static void IsPrimeBatchGpu(ReadOnlySpan values, Span results) { 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; - var inputView = input.View; - var outputView = output.View; - int pos = 0; + while (pos < totalLength) { - int remaining = totalLength - pos; - int count = remaining > batchSize ? batchSize : remaining; + ulong currentValue = values[pos]; + byte trailingDigit = (byte)(currentValue % 10UL); + var gpu = PrimeTesterGpuContextPool.Rent(trailingDigit, GpuBatchSize); + var accelerator = gpu.Accelerator; + var kernel = gpu.Kernel; + var input = gpu.Input; + var output = gpu.Output; + var inputView = input.View; + var outputView = output.View; - var valueSlice = values.Slice(pos, count); - ref ulong valueRef = ref MemoryMarshal.GetReference(valueSlice); - inputView.CopyFromCPU(ref valueRef, count); + int blockStart = pos; + while (blockStart < totalLength) + { + byte blockDigit = (byte)(values[blockStart] % 10UL); + if (blockDigit != trailingDigit) + { + break; + } - kernel( - count, - inputView, - 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, - outputView); - accelerator.Synchronize(); - var resultSlice = results.Slice(pos, count); - ref byte resultRef = ref MemoryMarshal.GetReference(resultSlice); - outputView.CopyToCPU(ref resultRef, count); + int limit = Math.Min(batchSize, totalLength - blockStart); + int count = 0; + while (count < limit && (byte)(values[blockStart + count] % 10UL) == trailingDigit) + { + count++; + } - pos += count; + if (count == 0) + { + break; + } + + var valueSlice = values.Slice(blockStart, count); + ref ulong valueRef = ref MemoryMarshal.GetReference(valueSlice); + inputView.CopyFromCPU(ref valueRef, count); + + kernel( + count, + inputView, + 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, + outputView); + accelerator.Synchronize(); + var resultSlice = results.Slice(blockStart, count); + ref byte resultRef = ref MemoryMarshal.GetReference(resultSlice); + outputView.CopyToCPU(ref resultRef, count); + + blockStart += count; + } + + gpu.Dispose(); + pos = blockStart; } - gpu.Dispose(); limiter.Dispose(); } internal static class PrimeTesterGpuContextPool { - private static readonly ConcurrentQueue Pool = new(); + internal enum HeuristicGpuTableScope : byte + { + GroupAB = 0, + GroupABWithCombined = 1, + } + + private static readonly byte[] SupportedTrailingDigits = [1, 3, 7, 9]; + private const int ScopeCount = 2; + private static readonly ConcurrentQueue[,] Pools; private static readonly object SharedTablesLock = new(); - private static readonly Dictionary SharedTables = new(); + private static readonly Dictionary SharedTables = new(); + private static readonly Dictionary<(string Key, byte Digit, HeuristicGpuTableScope Scope), TrailingDigitTables> DigitTables = new(); - private static string CreateAcceleratorKey(Accelerator accelerator) + static PrimeTesterGpuContextPool() { - return accelerator.AcceleratorType + ":" + accelerator.Name; - } - - private static SharedHeuristicGpuTables RentSharedTables(Accelerator accelerator) - { - string key = CreateAcceleratorKey(accelerator); - - lock (SharedTablesLock) + Pools = new ConcurrentQueue[SupportedTrailingDigits.Length, ScopeCount]; + for (int digitIndex = 0; digitIndex < SupportedTrailingDigits.Length; digitIndex++) { - if (!SharedTables.TryGetValue(key, out var tables)) + for (int scopeIndex = 0; scopeIndex < ScopeCount; scopeIndex++) { - tables = new SharedHeuristicGpuTables(accelerator, key); - SharedTables.Add(key, tables); + Pools[digitIndex, scopeIndex] = new ConcurrentQueue(); } - - tables.AddReference(); - return tables; } } - private static void ReleaseSharedTables(SharedHeuristicGpuTables tables) - { - if (tables is null) - { - return; - } + internal static ReadOnlySpan SupportedDigits => SupportedTrailingDigits; - lock (SharedTablesLock) + private static int GetPoolIndex(byte trailingDigit) + { + return trailingDigit switch { - tables.Release(); - } + 1 => 0, + 3 => 1, + 7 => 2, + 9 => 3, + _ => throw new ArgumentOutOfRangeException(nameof(trailingDigit), trailingDigit, "Only trailing digits 1, 3, 7, and 9 are supported."), + }; } - internal static PrimeTesterGpuContextLease Rent(int minBufferCapacity = 1) + private static int GetScopeIndex(HeuristicGpuTableScope scope) { - if (!Pool.TryDequeue(out var lease)) + return scope switch { - lease = new PrimeTesterGpuContextLease(minBufferCapacity); + HeuristicGpuTableScope.GroupAB => 0, + HeuristicGpuTableScope.GroupABWithCombined => 1, + _ => throw new ArgumentOutOfRangeException(nameof(scope), scope, "Unsupported heuristic table scope."), + }; + } + + internal static PrimeTesterGpuContextLease Rent(byte trailingDigit, int minBufferCapacity = 1, HeuristicGpuTableScope tableScope = HeuristicGpuTableScope.GroupAB) + { + var pool = Pools[GetPoolIndex(trailingDigit), GetScopeIndex(tableScope)]; + if (!pool.TryDequeue(out var lease)) + { + lease = new PrimeTesterGpuContextLease(trailingDigit, minBufferCapacity, tableScope); } else { @@ -285,18 +327,32 @@ public sealed class PrimeTester internal static void Return(PrimeTesterGpuContextLease lease) { lease.Accelerator.Synchronize(); - Pool.Enqueue(lease); + Pools[GetPoolIndex(lease.TrailingDigit), GetScopeIndex(lease.TableScope)].Enqueue(lease); } internal static void DisposeAll() { - while (Pool.TryDequeue(out var lease)) + for (int digitIndex = 0; digitIndex < SupportedTrailingDigits.Length; digitIndex++) { - lease.DisposeResources(); + for (int scopeIndex = 0; scopeIndex < ScopeCount; scopeIndex++) + { + var pool = Pools[digitIndex, scopeIndex]; + while (pool.TryDequeue(out var lease)) + { + lease.DisposeResources(); + } + } } lock (SharedTablesLock) { + foreach (var entry in DigitTables.Values) + { + entry.Dispose(); + } + + DigitTables.Clear(); + foreach (var entry in SharedTables.Values) { entry.Dispose(); @@ -314,150 +370,254 @@ public sealed class PrimeTester } var retained = new List(); - while (Pool.TryDequeue(out var lease)) + for (int digitIndex = 0; digitIndex < SupportedTrailingDigits.Length; digitIndex++) { - if (lease.Accelerator == accelerator) + for (int scopeIndex = 0; scopeIndex < ScopeCount; scopeIndex++) { - lease.DisposeResources(); - } - else - { - retained.Add(lease); + var pool = Pools[digitIndex, scopeIndex]; + while (pool.TryDequeue(out var lease)) + { + if (lease.Accelerator == accelerator) + { + lease.DisposeResources(); + } + else + { + retained.Add(lease); + } + } + + int retainedCount = retained.Count; + for (int i = 0; i < retainedCount; i++) + { + pool.Enqueue(retained[i]); + } + + retained.Clear(); } } - int retainedCount = retained.Count; - for (int i = 0; i < retainedCount; i++) + lock (SharedTablesLock) { - Pool.Enqueue(retained[i]); + string key = CreateAcceleratorKey(accelerator); + + if (SharedTables.Remove(key, out var shared)) + { + shared.Dispose(); + } + + if (DigitTables.Count > 0) + { + var toRemove = new List<(string Key, byte Digit, HeuristicGpuTableScope Scope)>(); + foreach (var entry in DigitTables) + { + if (entry.Key.Key == key) + { + entry.Value.Dispose(); + toRemove.Add(entry.Key); + } + } + + for (int i = 0; i < toRemove.Count; i++) + { + DigitTables.Remove(toRemove[i]); + } + } + } + } + + private static string CreateAcceleratorKey(Accelerator accelerator) + { + return accelerator.AcceleratorType + ":" + accelerator.Name; + } + + private static SharedKernelTables RentSharedTables(Accelerator accelerator) + { + string key = CreateAcceleratorKey(accelerator); + + lock (SharedTablesLock) + { + if (!SharedTables.TryGetValue(key, out var tables)) + { + tables = new SharedKernelTables(accelerator, key); + SharedTables.Add(key, tables); + } + + tables.AddReference(); + return tables; + } + } + + private static void ReleaseSharedTables(SharedKernelTables tables) + { + if (tables is null) + { + return; + } + + lock (SharedTablesLock) + { + if (tables.Release() && SharedTables.Remove(tables.Key, out _)) + { + tables.Dispose(); + } + } + } + + private static TrailingDigitTables RentTrailingDigitTables(Accelerator accelerator, byte trailingDigit, HeuristicGpuTableScope scope) + { + string key = CreateAcceleratorKey(accelerator); + var compositeKey = (key, trailingDigit, scope); + + lock (SharedTablesLock) + { + if (!DigitTables.TryGetValue(compositeKey, out var tables)) + { + tables = new TrailingDigitTables(accelerator, key, trailingDigit, scope); + DigitTables.Add(compositeKey, tables); + } + + tables.AddReference(); + return tables; + } + } + + private static void ReleaseTrailingDigitTables(TrailingDigitTables tables) + { + if (tables is null) + { + return; + } + + lock (SharedTablesLock) + { + var compositeKey = (tables.Key, tables.TrailingDigit, tables.Scope); + if (tables.Release() && DigitTables.Remove(compositeKey)) + { + tables.Dispose(); + } } } internal sealed class PrimeTesterGpuContextLease : IDisposable + { + private bool _returned; + private readonly SharedKernelTables _sharedTables; + private readonly TrailingDigitTables _digitTables; + private readonly HeuristicGpuDivisorTables _heuristicDivisorTables; + private readonly HeuristicGpuCombinedDivisorTables _heuristicCombinedTables; + + internal readonly Context AcceleratorContext; + public readonly Accelerator Accelerator; + public readonly byte TrailingDigit; + public readonly HeuristicGpuTableScope TableScope; + public readonly Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> Kernel; + public readonly Action, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables, HeuristicGpuCombinedDivisorTables> HeuristicTrialDivisionKernel; + public MemoryBuffer1D Input = null!; + public MemoryBuffer1D Output = null!; + public MemoryBuffer1D HeuristicFlag = null!; + public int BufferCapacity; + + internal PrimeTesterGpuContextLease(byte trailingDigit, int minBufferCapacity, HeuristicGpuTableScope tableScope) { + TrailingDigit = trailingDigit; + TableScope = tableScope; - private bool _returned; - private readonly SharedHeuristicGpuTables _sharedTables; - private readonly HeuristicGpuDivisorTables _heuristicDivisorTables; + AcceleratorContext = Context.CreateDefault(); + Accelerator = AcceleratorContext.GetPreferredDevice(false).CreateAccelerator(AcceleratorContext); + Kernel = Accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); + HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables, HeuristicGpuCombinedDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel); - internal readonly Context AcceleratorContext; - public readonly Accelerator Accelerator; - public readonly Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> Kernel; - public readonly Action, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables> HeuristicTrialDivisionKernel; - public MemoryBuffer1D Input = null!; - public MemoryBuffer1D Output = null!; - public MemoryBuffer1D HeuristicFlag = null!; - public int BufferCapacity; + _sharedTables = RentSharedTables(Accelerator); + _digitTables = RentTrailingDigitTables(Accelerator, trailingDigit, tableScope); - public MemoryBuffer1D DevicePrimesDefault => _sharedTables.DevicePrimesDefault; - public MemoryBuffer1D DevicePrimesLastOne => _sharedTables.DevicePrimesLastOne; - public MemoryBuffer1D DevicePrimesLastSeven => _sharedTables.DevicePrimesLastSeven; - public MemoryBuffer1D DevicePrimesLastThree => _sharedTables.DevicePrimesLastThree; - public MemoryBuffer1D DevicePrimesLastNine => _sharedTables.DevicePrimesLastNine; - public MemoryBuffer1D DevicePrimesPow2Default => _sharedTables.DevicePrimesPow2Default; - public MemoryBuffer1D DevicePrimesPow2LastOne => _sharedTables.DevicePrimesPow2LastOne; - public MemoryBuffer1D DevicePrimesPow2LastSeven => _sharedTables.DevicePrimesPow2LastSeven; - public MemoryBuffer1D DevicePrimesPow2LastThree => _sharedTables.DevicePrimesPow2LastThree; - public MemoryBuffer1D DevicePrimesPow2LastNine => _sharedTables.DevicePrimesPow2LastNine; - public MemoryBuffer1D HeuristicGroupADivisors => _sharedTables.HeuristicGroupADivisors; - public MemoryBuffer1D HeuristicGroupADivisorSquares => _sharedTables.HeuristicGroupADivisorSquares; - public MemoryBuffer1D HeuristicGroupBDivisorsEnding1 => _sharedTables.HeuristicGroupBDivisorsEnding1; - public MemoryBuffer1D HeuristicGroupBDivisorSquaresEnding1 => _sharedTables.HeuristicGroupBDivisorSquaresEnding1; - public MemoryBuffer1D HeuristicGroupBDivisorsEnding7 => _sharedTables.HeuristicGroupBDivisorsEnding7; - public MemoryBuffer1D HeuristicGroupBDivisorSquaresEnding7 => _sharedTables.HeuristicGroupBDivisorSquaresEnding7; - public MemoryBuffer1D HeuristicGroupBDivisorsEnding9 => _sharedTables.HeuristicGroupBDivisorsEnding9; - public MemoryBuffer1D HeuristicGroupBDivisorSquaresEnding9 => _sharedTables.HeuristicGroupBDivisorSquaresEnding9; - public MemoryBuffer1D HeuristicCombinedDivisorsEnding1 => _sharedTables.HeuristicCombinedDivisorsEnding1; - public MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding1 => _sharedTables.HeuristicCombinedDivisorSquaresEnding1; - public MemoryBuffer1D HeuristicCombinedDivisorsEnding3 => _sharedTables.HeuristicCombinedDivisorsEnding3; - public MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding3 => _sharedTables.HeuristicCombinedDivisorSquaresEnding3; - public MemoryBuffer1D HeuristicCombinedDivisorsEnding7 => _sharedTables.HeuristicCombinedDivisorsEnding7; - public MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding7 => _sharedTables.HeuristicCombinedDivisorSquaresEnding7; - public MemoryBuffer1D HeuristicCombinedDivisorsEnding9 => _sharedTables.HeuristicCombinedDivisorsEnding9; - public MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding9 => _sharedTables.HeuristicCombinedDivisorSquaresEnding9; + _heuristicDivisorTables = new HeuristicGpuDivisorTables( + _sharedTables.HeuristicGroupADivisors.View, + _digitTables.GroupBDivisorsEnding1View, + _digitTables.GroupBDivisorsEnding7View, + _digitTables.GroupBDivisorsEnding9View); + HeuristicGpuDivisorTables.InitializeShared(in _heuristicDivisorTables); - public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables; - - internal PrimeTesterGpuContextLease(int minBufferCapacity) + if (_digitTables.HasCombinedTables) { - AcceleratorContext = Context.CreateDefault(); - Accelerator = AcceleratorContext.GetPreferredDevice(false).CreateAccelerator(AcceleratorContext); - Kernel = Accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(PrimeTesterKernels.SmallPrimeSieveKernel); - HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel); - - _sharedTables = RentSharedTables(Accelerator); - - _heuristicDivisorTables = new HeuristicGpuDivisorTables( - _sharedTables.HeuristicCombinedDivisorsEnding1.View, - _sharedTables.HeuristicCombinedDivisorsEnding3.View, - _sharedTables.HeuristicCombinedDivisorsEnding7.View, - _sharedTables.HeuristicCombinedDivisorsEnding9.View, - _sharedTables.HeuristicCombinedDivisorSquaresEnding1.View, - _sharedTables.HeuristicCombinedDivisorSquaresEnding3.View, - _sharedTables.HeuristicCombinedDivisorSquaresEnding7.View, - _sharedTables.HeuristicCombinedDivisorSquaresEnding9.View, - _sharedTables.HeuristicGroupADivisors.View, - _sharedTables.HeuristicGroupADivisorSquares.View, - _sharedTables.HeuristicGroupBDivisorsEnding1.View, - _sharedTables.HeuristicGroupBDivisorSquaresEnding1.View, - _sharedTables.HeuristicGroupBDivisorsEnding7.View, - _sharedTables.HeuristicGroupBDivisorSquaresEnding7.View, - _sharedTables.HeuristicGroupBDivisorsEnding9.View, - _sharedTables.HeuristicGroupBDivisorSquaresEnding9.View); - HeuristicGpuDivisorTables.InitializeShared(in _heuristicDivisorTables); - - EnsureCapacity(minBufferCapacity); - _returned = false; + _heuristicCombinedTables = new HeuristicGpuCombinedDivisorTables( + _digitTables.CombinedDivisorsEnding1View, + _digitTables.CombinedDivisorsEnding3View, + _digitTables.CombinedDivisorsEnding7View, + _digitTables.CombinedDivisorsEnding9View); + HeuristicGpuCombinedDivisorTables.InitializeShared(in _heuristicCombinedTables); + } + else + { + _heuristicCombinedTables = HeuristicGpuCombinedDivisorTables.Shared; } - public void EnsureCapacity(int minCapacity) - { - if (minCapacity <= BufferCapacity) - { - return; - } - - Input?.Dispose(); - Output?.Dispose(); - HeuristicFlag?.Dispose(); - Input = Accelerator.Allocate1D(minCapacity); - Output = Accelerator.Allocate1D(minCapacity); - HeuristicFlag = Accelerator.Allocate1D(minCapacity); - BufferCapacity = minCapacity; - } - - internal void ResetReturnFlag() - { - _returned = false; - } - - public void Dispose() - { - if (_returned) - { - return; - } - - _returned = true; - PrimeTesterGpuContextPool.Return(this); - } - - internal void DisposeResources() - { - Input?.Dispose(); - Output?.Dispose(); - HeuristicFlag?.Dispose(); - ReleaseSharedTables(_sharedTables); - Accelerator.Dispose(); - AcceleratorContext.Dispose(); - } + EnsureCapacity(minBufferCapacity); + _returned = false; } - private sealed class SharedHeuristicGpuTables + public MemoryBuffer1D DevicePrimesDefault => _sharedTables.DevicePrimesDefault; + public MemoryBuffer1D DevicePrimesLastOne => _sharedTables.DevicePrimesLastOne; + public MemoryBuffer1D DevicePrimesLastSeven => _sharedTables.DevicePrimesLastSeven; + public MemoryBuffer1D DevicePrimesLastThree => _sharedTables.DevicePrimesLastThree; + public MemoryBuffer1D DevicePrimesLastNine => _sharedTables.DevicePrimesLastNine; + public MemoryBuffer1D DevicePrimesPow2Default => _sharedTables.DevicePrimesPow2Default; + public MemoryBuffer1D DevicePrimesPow2LastOne => _sharedTables.DevicePrimesPow2LastOne; + public MemoryBuffer1D DevicePrimesPow2LastSeven => _sharedTables.DevicePrimesPow2LastSeven; + public MemoryBuffer1D DevicePrimesPow2LastThree => _sharedTables.DevicePrimesPow2LastThree; + public MemoryBuffer1D DevicePrimesPow2LastNine => _sharedTables.DevicePrimesPow2LastNine; + + public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables; + public HeuristicGpuCombinedDivisorTables HeuristicCombinedTables => _heuristicCombinedTables; + + internal void EnsureCapacity(int minCapacity) + { + if (minCapacity <= BufferCapacity) + { + return; + } + + Input?.Dispose(); + Output?.Dispose(); + HeuristicFlag?.Dispose(); + Input = Accelerator.Allocate1D(minCapacity); + Output = Accelerator.Allocate1D(minCapacity); + HeuristicFlag = Accelerator.Allocate1D(minCapacity); + BufferCapacity = minCapacity; + } + + internal void ResetReturnFlag() + { + _returned = false; + } + + public void Dispose() + { + if (_returned) + { + return; + } + + _returned = true; + PrimeTesterGpuContextPool.Return(this); + } + + internal void DisposeResources() + { + Input?.Dispose(); + Output?.Dispose(); + HeuristicFlag?.Dispose(); + ReleaseTrailingDigitTables(_digitTables); + ReleaseSharedTables(_sharedTables); + Accelerator.Dispose(); + AcceleratorContext.Dispose(); + } + } + private sealed class SharedKernelTables { private int _referenceCount; - internal SharedHeuristicGpuTables(Accelerator accelerator, string key) + internal SharedKernelTables(Accelerator accelerator, string key) { Key = key; @@ -502,42 +662,7 @@ public sealed class PrimeTester DevicePrimesPow2LastNine.View.CopyFromCPU(primesPow2LastNine); HeuristicPrimeSieves.EnsureInitialized(); - HeuristicCombinedPrimeTester.EnsureInitialized(); - - var heuristicGroupA = HeuristicPrimeSieves.GroupADivisors; - HeuristicGroupADivisors = CopySpanToDevice(accelerator, heuristicGroupA); - - var heuristicGroupASquares = HeuristicPrimeSieves.GroupADivisorSquares; - HeuristicGroupADivisorSquares = CopySpanToDevice(accelerator, heuristicGroupASquares); - - HeuristicGroupBDivisorsEnding1 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastOneWithoutLastThree); - HeuristicGroupBDivisorSquaresEnding1 = CopySpanToDevice(accelerator, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree); - - HeuristicGroupBDivisorsEnding7 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree); - HeuristicGroupBDivisorSquaresEnding7 = CopySpanToDevice(accelerator, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree); - - HeuristicGroupBDivisorsEnding9 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastNineWithoutLastThree); - HeuristicGroupBDivisorSquaresEnding9 = CopySpanToDevice(accelerator, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree); - - var combinedEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1Span; - HeuristicCombinedDivisorsEnding1 = CopySpanToDevice(accelerator, combinedEnding1); - var combinedSquaresEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1SquaresSpan; - HeuristicCombinedDivisorSquaresEnding1 = CopySpanToDevice(accelerator, combinedSquaresEnding1); - - var combinedEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3Span; - HeuristicCombinedDivisorsEnding3 = CopySpanToDevice(accelerator, combinedEnding3); - var combinedSquaresEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3SquaresSpan; - HeuristicCombinedDivisorSquaresEnding3 = CopySpanToDevice(accelerator, combinedSquaresEnding3); - - var combinedEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7Span; - HeuristicCombinedDivisorsEnding7 = CopySpanToDevice(accelerator, combinedEnding7); - var combinedSquaresEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7SquaresSpan; - HeuristicCombinedDivisorSquaresEnding7 = CopySpanToDevice(accelerator, combinedSquaresEnding7); - - var combinedEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9Span; - HeuristicCombinedDivisorsEnding9 = CopySpanToDevice(accelerator, combinedEnding9); - var combinedSquaresEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9SquaresSpan; - HeuristicCombinedDivisorSquaresEnding9 = CopySpanToDevice(accelerator, combinedSquaresEnding9); + HeuristicGroupADivisors = CopySpanToDevice(accelerator, HeuristicPrimeSieves.GroupADivisors); } internal string Key { get; } @@ -552,62 +677,20 @@ public sealed class PrimeTester internal MemoryBuffer1D DevicePrimesPow2LastThree { get; } internal MemoryBuffer1D DevicePrimesPow2LastNine { get; } internal MemoryBuffer1D HeuristicGroupADivisors { get; } - internal MemoryBuffer1D HeuristicGroupADivisorSquares { get; } - internal MemoryBuffer1D HeuristicGroupBDivisorsEnding1 { get; } - internal MemoryBuffer1D HeuristicGroupBDivisorSquaresEnding1 { get; } - internal MemoryBuffer1D HeuristicGroupBDivisorsEnding7 { get; } - internal MemoryBuffer1D HeuristicGroupBDivisorSquaresEnding7 { get; } - internal MemoryBuffer1D HeuristicGroupBDivisorsEnding9 { get; } - internal MemoryBuffer1D HeuristicGroupBDivisorSquaresEnding9 { get; } - internal MemoryBuffer1D HeuristicCombinedDivisorsEnding1 { get; } - internal MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding1 { get; } - internal MemoryBuffer1D HeuristicCombinedDivisorsEnding3 { get; } - internal MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding3 { get; } - internal MemoryBuffer1D HeuristicCombinedDivisorsEnding7 { get; } - internal MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding7 { get; } - internal MemoryBuffer1D HeuristicCombinedDivisorsEnding9 { get; } - internal MemoryBuffer1D HeuristicCombinedDivisorSquaresEnding9 { get; } internal void AddReference() { _referenceCount++; } - internal void Release() + internal bool Release() { if (_referenceCount > 0) { _referenceCount--; } - } - private static MemoryBuffer1D CopySpanToDevice(Accelerator accelerator, ReadOnlySpan span) - { - var buffer = accelerator.Allocate1D(span.Length); - if (!span.IsEmpty) - { - ref ulong sourceRef = ref MemoryMarshal.GetReference(span); - buffer.View.CopyFromCPU(ref sourceRef, span.Length); - } - - return buffer; - } - - private static MemoryBuffer1D CopyUintSpanToDevice(Accelerator accelerator, ReadOnlySpan span) - { - var buffer = accelerator.Allocate1D(span.Length); - if (!span.IsEmpty) - { - var converted = new ulong[span.Length]; - for (int i = 0; i < span.Length; i++) - { - converted[i] = span[i]; - } - - buffer.View.CopyFromCPU(converted); - } - - return buffer; + return _referenceCount == 0; } internal void Dispose() @@ -623,23 +706,123 @@ public sealed class PrimeTester DevicePrimesPow2LastThree.Dispose(); DevicePrimesPow2LastNine.Dispose(); HeuristicGroupADivisors.Dispose(); - HeuristicGroupADivisorSquares.Dispose(); - HeuristicGroupBDivisorsEnding1.Dispose(); - HeuristicGroupBDivisorSquaresEnding1.Dispose(); - HeuristicGroupBDivisorsEnding7.Dispose(); - HeuristicGroupBDivisorSquaresEnding7.Dispose(); - HeuristicGroupBDivisorsEnding9.Dispose(); - HeuristicGroupBDivisorSquaresEnding9.Dispose(); - HeuristicCombinedDivisorsEnding1.Dispose(); - HeuristicCombinedDivisorSquaresEnding1.Dispose(); - HeuristicCombinedDivisorsEnding3.Dispose(); - HeuristicCombinedDivisorSquaresEnding3.Dispose(); - HeuristicCombinedDivisorsEnding7.Dispose(); - HeuristicCombinedDivisorSquaresEnding7.Dispose(); - HeuristicCombinedDivisorsEnding9.Dispose(); - HeuristicCombinedDivisorSquaresEnding9.Dispose(); } } + + private sealed class TrailingDigitTables + { + private int _referenceCount; + + internal TrailingDigitTables(Accelerator accelerator, string key, byte trailingDigit, HeuristicGpuTableScope scope) + { + Key = key; + TrailingDigit = trailingDigit; + Scope = scope; + + HeuristicPrimeSieves.EnsureInitialized(); + HeuristicCombinedPrimeTester.EnsureInitialized(); + + if (trailingDigit == 1 || trailingDigit == 7 || trailingDigit == 9) + { + GroupBDivisorsEnding1 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastOneWithoutLastThree); + } + + if (trailingDigit == 3 || trailingDigit == 7 || trailingDigit == 9) + { + GroupBDivisorsEnding7 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree); + } + + if (trailingDigit == 1 || trailingDigit == 3 || trailingDigit == 9) + { + GroupBDivisorsEnding9 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastNineWithoutLastThree); + } + + if (scope == HeuristicGpuTableScope.GroupABWithCombined) + { + CombinedDivisorsEnding1 = trailingDigit == 1 ? CopySpanToDevice(accelerator, HeuristicCombinedPrimeTester.CombinedDivisorsEnding1Span) : null; + CombinedDivisorsEnding3 = trailingDigit == 3 ? CopySpanToDevice(accelerator, HeuristicCombinedPrimeTester.CombinedDivisorsEnding3Span) : null; + CombinedDivisorsEnding7 = trailingDigit == 7 ? CopySpanToDevice(accelerator, HeuristicCombinedPrimeTester.CombinedDivisorsEnding7Span) : null; + CombinedDivisorsEnding9 = trailingDigit == 9 ? CopySpanToDevice(accelerator, HeuristicCombinedPrimeTester.CombinedDivisorsEnding9Span) : null; + } + } + + internal string Key { get; } + internal byte TrailingDigit { get; } + internal HeuristicGpuTableScope Scope { get; } + + internal bool HasCombinedTables => Scope == HeuristicGpuTableScope.GroupABWithCombined; + + internal MemoryBuffer1D? CombinedDivisorsEnding1 { get; } + internal MemoryBuffer1D? CombinedDivisorsEnding3 { get; } + internal MemoryBuffer1D? CombinedDivisorsEnding7 { get; } + internal MemoryBuffer1D? CombinedDivisorsEnding9 { get; } + internal MemoryBuffer1D? GroupBDivisorsEnding1 { get; } + internal MemoryBuffer1D? GroupBDivisorsEnding7 { get; } + internal MemoryBuffer1D? GroupBDivisorsEnding9 { get; } + + internal ArrayView1D CombinedDivisorsEnding1View => CombinedDivisorsEnding1?.View ?? ArrayView1D.Empty; + internal ArrayView1D CombinedDivisorsEnding3View => CombinedDivisorsEnding3?.View ?? ArrayView1D.Empty; + internal ArrayView1D CombinedDivisorsEnding7View => CombinedDivisorsEnding7?.View ?? ArrayView1D.Empty; + internal ArrayView1D CombinedDivisorsEnding9View => CombinedDivisorsEnding9?.View ?? ArrayView1D.Empty; + internal ArrayView1D GroupBDivisorsEnding1View => GroupBDivisorsEnding1?.View ?? ArrayView1D.Empty; + internal ArrayView1D GroupBDivisorsEnding7View => GroupBDivisorsEnding7?.View ?? ArrayView1D.Empty; + internal ArrayView1D GroupBDivisorsEnding9View => GroupBDivisorsEnding9?.View ?? ArrayView1D.Empty; + + internal void AddReference() + { + _referenceCount++; + } + + internal bool Release() + { + if (_referenceCount > 0) + { + _referenceCount--; + } + + return _referenceCount == 0; + } + + internal void Dispose() + { + CombinedDivisorsEnding1?.Dispose(); + CombinedDivisorsEnding3?.Dispose(); + CombinedDivisorsEnding7?.Dispose(); + CombinedDivisorsEnding9?.Dispose(); + GroupBDivisorsEnding1?.Dispose(); + GroupBDivisorsEnding7?.Dispose(); + GroupBDivisorsEnding9?.Dispose(); + } + } + + private static MemoryBuffer1D CopySpanToDevice(Accelerator accelerator, ReadOnlySpan span) + { + var buffer = accelerator.Allocate1D(span.Length); + if (!span.IsEmpty) + { + ref ulong source = ref MemoryMarshal.GetReference(span); + buffer.View.CopyFromCPU(ref source, span.Length); + } + + return buffer; + } + + private static MemoryBuffer1D CopyUintSpanToDevice(Accelerator accelerator, ReadOnlySpan span) + { + var buffer = accelerator.Allocate1D(span.Length); + if (!span.IsEmpty) + { + var converted = new ulong[span.Length]; + for (int i = 0; i < span.Length; i++) + { + converted[i] = span[i]; + } + + buffer.View.CopyFromCPU(converted); + } + + return buffer; + } } // Expose cache clearing for accelerator disposal coordination @@ -672,7 +855,7 @@ public sealed class PrimeTester // TODO: Route this batch helper through the shared GPU kernel pool from // GpuUInt128BinaryGcdBenchmarks so we reuse cached kernels, pinned host buffers, // and divisor-cycle staging instead of allocating new device buffers per call. - var gpu = PrimeTesterGpuContextPool.Rent(); + var gpu = PrimeTesterGpuContextPool.Rent(1); var accelerator = gpu.Accelerator; var kernel = accelerator.LoadAutoGroupedStreamKernel, ArrayView>(PrimeTesterKernels.SharesFactorKernel); diff --git a/docs/gpu-context-rework-plan.md b/docs/gpu-context-rework-plan.md new file mode 100644 index 0000000..84f5145 --- /dev/null +++ b/docs/gpu-context-rework-plan.md @@ -0,0 +1,8 @@ +# GPU context pooling and heuristic divisor updates + +- [x] Review existing GPU context pooling in `PerfectNumbers.Core/PrimeTester.cs` and determine shared table allocations that can be scoped per trailing digit. +- [x] Split `PrimeTesterGpuContextPool` into trailing-digit specific pools so each lease only uploads the divisor tables required for that digit. +- [x] Update GPU warm-up and rental code paths to pick pools based on `n % 10` and ensure thread-safe reuse. +- [x] Replace square-table usage in GPU heuristic trial division with square-root limits so divisor tables no longer need squared values on the accelerator. +- [x] Remove GPU square buffers and adjust kernels, table types, and callers accordingly. +- [x] Build solution to confirm the new pooling and divisor logic compiles; tests documented in summary.