Compare commits

..
Author SHA1 Message Date
mlemanczyk fc22c01ec8 Various improvements to make sure scratch buffers don't override each others memory 2025-11-03 23:49:55 +01:00
Marek Lemańczyk dcda4b5cba Merge branch 'codex/optimize-evenperfectbitscanner-for-static-arrays' from github 2025-11-03 18:39:00 +01:00
Marek Lemańczyk 507daaddb9 Merge pull request #253 from mlemanczyk/codex/optimize-evenperfectbitscanner-for-primetestergpucontextleas
Add GPU lease warm-up and shared limiter
2025-11-02 14:02:42 +01:00
Marek Lemańczyk 1cbe7bf869 Use GpuPrimeWorkLimiter for GPU lease limiting 2025-11-02 14:02:15 +01:00
Marek Lemańczyk 00d4e9bde2 Add GPU lease warm-up and shared limiter 2025-11-02 13:47:57 +01:00
mlemanczyk 0e7bbae132 GPU contexts and accelerators made shared to prevent application crashes with high no. of threads. 2025-11-02 12:51:31 +01:00
mlemanczyk b33a3c8e75 Compilation error fixed 2025-11-02 11:25:41 +01:00
mlemanczyk 07be671726 Various small optimizations 2025-11-02 11:24:18 +01:00
Marek Lemańczyk 76dfe108a9 Address feedback on GPU pooling and heuristic factoring 2025-11-02 11:22:49 +01:00
Marek Lemańczyk 5339c3c304 Use span growth instead of dictionaries in partial factor 2025-11-02 11:22:38 +01:00
Marek Lemańczyk ab6a416de2 Warm GPU resources before thread startup 2025-11-02 11:22:28 +01:00
mlemanczyk 2281d9535e Revert "Merge pull request #251 from mlemanczyk/codex/refactor-prime-checking-on-gpu-n5t2pm"
This reverts commit 936ce239db, reversing
changes made to e14b77e504.
2025-11-02 11:22:14 +01:00
27 changed files with 811 additions and 857 deletions

No files matched your search

@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using System.Threading;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using Open.Numeric.Primes; using Open.Numeric.Primes;
@@ -12,7 +9,6 @@ namespace EvenPerfectBitScanner.Benchmarks;
[MemoryDiagnoser] [MemoryDiagnoser]
public class PrimeTesterBenchmarks public class PrimeTesterBenchmarks
{ {
private static readonly HeuristicCombinedPrimeTester _heuristicCombinedTester = new();
private static readonly HeuristicPrimeTester _heuristicTester = new(); private static readonly HeuristicPrimeTester _heuristicTester = new();
private ulong[] _candidates = []; private ulong[] _candidates = [];
@@ -34,7 +30,7 @@ public class PrimeTesterBenchmarks
_ = Prime.Numbers.IsPrime(_candidates[0]); _ = Prime.Numbers.IsPrime(_candidates[0]);
} }
[Benchmark(Baseline = true)] [Benchmark]
public int HeuristicCpu() public int HeuristicCpu()
{ {
HeuristicPrimeTester tester = _heuristicTester; HeuristicPrimeTester tester = _heuristicTester;
@@ -104,18 +100,15 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
private static readonly PrimeTester _nonHeuristicTester = new();
[Benchmark] [Benchmark]
public int NonHeuristicGpu() public int NonHeuristicGpu()
{ {
PrimeTester tester = _nonHeuristicTester;
ulong[] values = _candidates; ulong[] values = _candidates;
int primeCount = 0; int primeCount = 0;
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (tester.IsPrimeGpu(values[i])) if (PrimeTester.IsPrimeGpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -124,7 +117,7 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
[Benchmark] [Benchmark(Baseline = true)]
public int NonHeuristicCpu() public int NonHeuristicCpu()
{ {
ulong[] values = _candidates; ulong[] values = _candidates;
+6 -8
View File
@@ -12,7 +12,7 @@ namespace EvenPerfectBitScanner;
internal static class Program internal static class Program
{ {
private static ThreadLocal<PrimeTester> PrimeTesters = null!; // private static ThreadLocal<PrimeTester> PrimeTesters = null!;
private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!; private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!;
private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP; private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP;
private static MersenneNumberDivisorGpuTester? _divisorTester; private static MersenneNumberDivisorGpuTester? _divisorTester;
@@ -58,14 +58,15 @@ internal static class Program
bool startPrimeProvided = _cliArguments.StartPrimeProvided; bool startPrimeProvided = _cliArguments.StartPrimeProvided;
int threadCount = Math.Max(1, _cliArguments.ThreadCount); int threadCount = Math.Max(1, _cliArguments.ThreadCount);
int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch); int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch);
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
UnboundedTaskScheduler.ConfigureThreadCount(threadCount); UnboundedTaskScheduler.ConfigureThreadCount(threadCount);
PrimeTester.GpuBatchSize = gpuPrimeBatch; PrimeTester.GpuBatchSize = gpuPrimeBatch;
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
GpuContextPool.WarmUpPool(threadCount); GpuContextPool.WarmUpPool(threadCount);
PrimeTester.WarmUpGpuKernels(threadCount); PrimeTester.WarmUpGpuKernels(gpuPrimeThreads);
Console.WriteLine("Starting up threads..."); Console.WriteLine("Starting up threads...");
_ = UnboundedTaskScheduler.Instance; _ = UnboundedTaskScheduler.Instance;
int blockSize = Math.Max(1, _cliArguments.BlockSize); int blockSize = Math.Max(1, _cliArguments.BlockSize);
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
GpuKernelType kernelType = _cliArguments.KernelType; GpuKernelType kernelType = _cliArguments.KernelType;
bool useBitTransform = _cliArguments.UseBitTransform; bool useBitTransform = _cliArguments.UseBitTransform;
bool useOrder = _cliArguments.UseOrder; bool useOrder = _cliArguments.UseOrder;
@@ -117,9 +118,6 @@ internal static class Program
return; return;
} }
// Apply GPU prime sieve runtime configuration
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
PrimeTester.GpuBatchSize = gpuPrimeBatch;
MersenneDivisorCycles mersenneDivisorCycles = new(); MersenneDivisorCycles mersenneDivisorCycles = new();
if (!File.Exists(cyclesPath) || continueCyclesGeneration) if (!File.Exists(cyclesPath) || continueCyclesGeneration)
@@ -178,7 +176,7 @@ internal static class Program
blockSize = useByDivisor ? 1 : blockSize; blockSize = useByDivisor ? 1 : blockSize;
CandidatesCalculator.Initialize(currentP); CandidatesCalculator.Initialize(currentP);
PrimeTesters = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true); // PrimeTesters = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
// Note: --primes-device controls default device for library kernels; p primality remains CPU here. // Note: --primes-device controls default device for library kernels; p primality remains CPU here.
// Initialize per-thread p residue tracker (Identity model) at currentP // Initialize per-thread p residue tracker (Identity model) at currentP
if (!useDivisor && !_cliArguments.UseByDivisor) if (!useDivisor && !_cliArguments.UseByDivisor)
@@ -628,7 +626,7 @@ internal static class Program
{ {
if (!(_runPrimesOnCpu if (!(_runPrimesOnCpu
? PrimeTester.IsPrime(p) ? PrimeTester.IsPrime(p)
: PrimeTesters.Value!.IsPrimeGpu(p))) : PrimeTester.IsPrimeGpu(p)))
{ {
_lastCompositeP = true; _lastCompositeP = true;
return false; return false;
@@ -18,19 +18,19 @@ public class GpuPrimeLimiterTests
var startedSecond = new TaskCompletionSource<bool>(); var startedSecond = new TaskCompletionSource<bool>();
var sw = new Stopwatch(); var sw = new Stopwatch();
var first = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
sw.Start(); sw.Start();
var secondTask = Task.Run(() => var secondTask = Task.Run(() =>
{ {
var second = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
try try
{ {
startedSecond.TrySetResult(true); startedSecond.TrySetResult(true);
} }
finally finally
{ {
second.Dispose(); GpuPrimeWorkLimiter.Release();
} }
}); });
@@ -43,7 +43,7 @@ public class GpuPrimeLimiterTests
finally finally
{ {
// Release first after the initial wait so the second task can proceed. // Release first after the initial wait so the second task can proceed.
first.Dispose(); GpuPrimeWorkLimiter.Release();
} }
await secondTask.WaitAsync(TimeSpan.FromSeconds(1)); await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
@@ -16,7 +16,7 @@ public class PrimeTesterGpuTests
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL]; ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
foreach (ulong prime in primes) foreach (ulong prime in primes)
{ {
tester.IsPrimeGpu(prime).Should().BeTrue(); PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
} }
} }
finally finally
@@ -34,7 +34,7 @@ public class PrimeTesterGpuTests
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL]; ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
foreach (ulong composite in composites) foreach (ulong composite in composites)
{ {
tester.IsPrimeGpu(composite).Should().BeFalse(); PrimeTester.IsPrimeGpu(composite).Should().BeFalse();
} }
} }
finally finally
@@ -39,7 +39,7 @@ public class PrimeTesterIsPrimeTests
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
tester.IsPrimeGpu(n).Should().Be(expected); PrimeTester.IsPrimeGpu(n).Should().Be(expected);
} }
[Fact] [Fact]
@@ -64,7 +64,7 @@ public class PrimeTesterIsPrimeTests
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true); var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
Parallel.For(0, primes.Length, i => Parallel.For(0, primes.Length, i =>
{ {
results[i] = testers.Value!.IsPrimeGpu(primes[i]); results[i] = PrimeTester.IsPrimeGpu(primes[i]);
}); });
results.Should().AllBeEquivalentTo(true); results.Should().AllBeEquivalentTo(true);
@@ -73,7 +73,7 @@ public class PrimeTesterIsPrimeTests
{ {
foreach (ulong prime in primes) foreach (ulong prime in primes)
{ {
testers.Value!.IsPrimeGpu(prime).Should().BeTrue(); PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
} }
}); });
} }
@@ -1,142 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal static class DivisorGeneratorGpuCache
{
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
public static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(Accelerator obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
private sealed class DivisorGeneratorGpuTables : IDisposable
{
internal DivisorGeneratorGpuTables(Accelerator accelerator)
{
SmallPrimes = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimes);
SmallPrimesPow2 = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2);
SmallPrimesLastOne = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastOne);
SmallPrimesPow2LastOne = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastOne);
SmallPrimesLastSeven = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastSeven);
SmallPrimesPow2LastSeven = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastSeven);
SmallPrimesLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastThree);
SmallPrimesPow2LastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastThree);
SmallPrimesLastNine = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastNine);
SmallPrimesPow2LastNine = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastNine);
SmallPrimesLastOneWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastOneWithoutLastThree);
SmallPrimesPow2LastOneWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree);
SmallPrimesLastSevenWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree);
SmallPrimesPow2LastSevenWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree);
SmallPrimesLastNineWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastNineWithoutLastThree);
SmallPrimesPow2LastNineWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree);
}
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimes { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2 { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastOne { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastOne { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastSeven { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastSeven { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastThree { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastNine { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastNine { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastOneWithoutLastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastOneWithoutLastThree { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastSevenWithoutLastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastSevenWithoutLastThree { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastNineWithoutLastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastNineWithoutLastThree { get; }
public void Dispose()
{
SmallPrimes.Dispose();
SmallPrimesPow2.Dispose();
SmallPrimesLastOne.Dispose();
SmallPrimesPow2LastOne.Dispose();
SmallPrimesLastSeven.Dispose();
SmallPrimesPow2LastSeven.Dispose();
SmallPrimesLastThree.Dispose();
SmallPrimesPow2LastThree.Dispose();
SmallPrimesLastNine.Dispose();
SmallPrimesPow2LastNine.Dispose();
SmallPrimesLastOneWithoutLastThree.Dispose();
SmallPrimesPow2LastOneWithoutLastThree.Dispose();
SmallPrimesLastSevenWithoutLastThree.Dispose();
SmallPrimesPow2LastSevenWithoutLastThree.Dispose();
SmallPrimesLastNineWithoutLastThree.Dispose();
SmallPrimesPow2LastNineWithoutLastThree.Dispose();
}
private static MemoryBuffer1D<uint, Stride1D.Dense> AllocateAndCopyUInt(Accelerator accelerator, uint[] source)
{
var buffer = accelerator.Allocate1D<uint>(source.Length);
if (source.Length > 0)
{
buffer.View.CopyFromCPU(source);
}
return buffer;
}
private static MemoryBuffer1D<ulong, Stride1D.Dense> AllocateAndCopyULong(Accelerator accelerator, ulong[] source)
{
var buffer = accelerator.Allocate1D<ulong>(source.Length);
if (source.Length > 0)
{
buffer.View.CopyFromCPU(source);
}
return buffer;
}
}
private static readonly ConcurrentDictionary<Accelerator, DivisorGeneratorGpuTables> Cache = new(AcceleratorReferenceComparer.Instance);
public static void WarmUpByDivisorTables(Accelerator accelerator)
{
if (accelerator is null || accelerator.AcceleratorType == AcceleratorType.CPU)
{
return;
}
Cache.GetOrAdd(accelerator, acc => new DivisorGeneratorGpuTables(acc));
}
public static void Clear(Accelerator accelerator)
{
if (accelerator is null)
{
return;
}
if (Cache.TryRemove(accelerator, out var tables))
{
tables.Dispose();
}
}
public static void DisposeAll()
{
foreach (var entry in Cache)
{
entry.Value.Dispose();
}
Cache.Clear();
}
}
+34 -24
View File
@@ -1,4 +1,3 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -12,14 +11,15 @@ public static class GpuContextPool
// Default device preference for generic GPU kernels (prime scans, NTT, etc.) // Default device preference for generic GPU kernels (prime scans, NTT, etc.)
internal sealed class PooledContext internal sealed class PooledContext
{ {
public Context Context { get; } public readonly Context Context;
public Accelerator Accelerator { get; } public readonly Accelerator Accelerator;
public PooledContext() public PooledContext(KernelContainer kernels)
{ {
Context = Context.CreateDefault(); Context = SharedGpuContext.Context;
Accelerator = Context.GetPreferredDevice(false).CreateAccelerator(Context); Accelerator = SharedGpuContext.Accelerator;
DivisorGeneratorGpuCache.WarmUpByDivisorTables(Accelerator); GpuStaticTableInitializer.EnsureStaticTables(kernels, Accelerator);
kernels.Dispose();
// NOTE: Avoid loading/compiling any kernel here to prevent implicit // NOTE: Avoid loading/compiling any kernel here to prevent implicit
// CL stream/queue creation during accelerator construction. // CL stream/queue creation during accelerator construction.
// Some OpenCL drivers are fragile when a queue is created immediately // Some OpenCL drivers are fragile when a queue is created immediately
@@ -33,11 +33,11 @@ public static class GpuContextPool
// Release PrimeTester GPU state for this accelerator (kernel/device primes) // Release PrimeTester GPU state for this accelerator (kernel/device primes)
// Clear any per-accelerator cached resources to avoid releasing // Clear any per-accelerator cached resources to avoid releasing
// after the accelerator is destroyed. // after the accelerator is destroyed.
DivisorGeneratorGpuCache.Clear(Accelerator);
PrimeTester.ClearGpuCaches(Accelerator); PrimeTester.ClearGpuCaches(Accelerator);
Accelerator.Dispose(); // These resources are shared between GPU leases
Context.Dispose(); // Accelerator.Dispose();
// Context.Dispose();
} }
} }
@@ -53,9 +53,10 @@ public static class GpuContextPool
} }
// Create a new accelerator when the pool does not have one available. // Create a new accelerator when the pool does not have one available.
return new GpuContextLease(new PooledContext()); return new GpuContextLease(new PooledContext(GpuKernelPool.GetKernels()));
} }
public static void WarmUpPool(int threadCount) public static void WarmUpPool(int threadCount)
{ {
if (threadCount <= WarmedGpuContextCount) if (threadCount <= WarmedGpuContextCount)
@@ -64,15 +65,16 @@ public static class GpuContextPool
} }
int toCreate = threadCount - WarmedGpuContextCount; int toCreate = threadCount - WarmedGpuContextCount;
Console.WriteLine($"Warming up GPU context pool to {toCreate} contexts...");
var contexts = new PooledContext[toCreate]; var contexts = new PooledContext[toCreate];
var pool = GpuPool;
PooledContext context;
for (int i = 0; i < toCreate; i++) for (int i = 0; i < toCreate; i++)
{ {
contexts[i] = new PooledContext(); KernelContainer kernels = GpuKernelPool.GetKernels();
} context = new PooledContext(kernels);
contexts[i] = context;
for (int i = 0; i < toCreate; i++) pool.Enqueue(context);
{
GpuPool.Enqueue(contexts[i]);
} }
WarmedGpuContextCount = threadCount; WarmedGpuContextCount = threadCount;
@@ -85,7 +87,6 @@ public static class GpuContextPool
gpu.Dispose(); gpu.Dispose();
} }
DivisorGeneratorGpuCache.DisposeAll();
PrimeTester.DisposeGpuContexts(); PrimeTester.DisposeGpuContexts();
WarmedGpuContextCount = 0; WarmedGpuContextCount = 0;
} }
@@ -96,21 +97,30 @@ public static class GpuContextPool
GpuPool.Enqueue(ctx); GpuPool.Enqueue(ctx);
} }
public readonly struct GpuContextLease public struct GpuContextLease
{ {
private readonly PooledContext _ctx; private PooledContext? _ctx;
private bool _disposed;
internal GpuContextLease(PooledContext ctx) internal GpuContextLease(PooledContext ctx)
{ {
_ctx = ctx; _ctx = ctx;
_disposed = false;
} }
public readonly Context Context => _ctx.Context; public Context Context => _ctx!.Context;
public readonly Accelerator Accelerator => _ctx.Accelerator; public Accelerator Accelerator => _ctx!.Accelerator;
public readonly void Dispose() public void Dispose()
{ {
Return(_ctx); if (_disposed)
{
return;
}
_disposed = true;
Return(_ctx!);
_ctx = null;
} }
} }
} }
+29 -17
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
using static PerfectNumbers.Core.Gpu.GpuContextPool; using static PerfectNumbers.Core.Gpu.GpuContextPool;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -10,25 +11,24 @@ public sealed class GpuKernelLease
{ {
private static readonly ConcurrentQueue<GpuKernelLease> Pool = new(); private static readonly ConcurrentQueue<GpuKernelLease> Pool = new();
private Action? _releaseLimiter;
private GpuContextLease? _gpu; private GpuContextLease? _gpu;
private KernelContainer? _kernels; public KernelContainer Kernels;
private AcceleratorStream? _stream; private AcceleratorStream? _stream;
private GpuKernelLease() private GpuKernelLease(KernelContainer kernels)
{ {
Kernels = kernels;
} }
internal static GpuKernelLease Rent(GpuPrimeWorkLimiter.Lease limiter, GpuContextLease gpu, KernelContainer kernels) internal static GpuKernelLease Rent(GpuContextLease gpu, KernelContainer kernels)
{ {
if (!Pool.TryDequeue(out var lease)) if (!Pool.TryDequeue(out var lease))
{ {
lease = new GpuKernelLease(); lease = new GpuKernelLease(kernels);
} }
lease._releaseLimiter = limiter.Dispose;
lease._gpu = gpu; lease._gpu = gpu;
lease._kernels = kernels; lease.Kernels = kernels;
lease._stream = null; lease._stream = null;
return lease; return lease;
} }
@@ -56,7 +56,7 @@ public sealed class GpuKernelLease
get get
{ {
var accel = Accelerator; // avoid capturing 'this' in lambda var accel = Accelerator; // avoid capturing 'this' in lambda
return KernelContainer.InitOnce(ref _kernels!.Order, () => return KernelContainer.InitOnce(ref Kernels!.Order, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -70,7 +70,7 @@ public sealed class GpuKernelLease
get get
{ {
var accel = Accelerator; var accel = Accelerator;
return KernelContainer.InitOnce(ref _kernels!.Pow2Mod, () => return KernelContainer.InitOnce(ref Kernels!.Pow2Mod, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -84,7 +84,7 @@ public sealed class GpuKernelLease
get get
{ {
var accel = Accelerator; var accel = Accelerator;
return KernelContainer.InitOnce(ref _kernels!.Incremental, () => return KernelContainer.InitOnce(ref Kernels!.Incremental, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -98,7 +98,7 @@ public sealed class GpuKernelLease
get get
{ {
var accel = Accelerator; var accel = Accelerator;
return KernelContainer.InitOnce(ref _kernels!.IncrementalOrder, () => return KernelContainer.InitOnce(ref Kernels!.IncrementalOrder, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -112,7 +112,7 @@ public sealed class GpuKernelLease
get get
{ {
var accel = Accelerator; var accel = Accelerator;
return KernelContainer.InitOnce(ref _kernels!.Pow2ModOrder, () => return KernelContainer.InitOnce(ref Kernels!.Pow2ModOrder, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -121,12 +121,26 @@ public sealed class GpuKernelLease
} }
} }
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, Stride1D.Dense>> SpecialMaxKernel
{
get
{
var accel = Accelerator;
return KernelContainer.InitOnce(ref Kernels!.SpecialMax, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, Stride1D.Dense>>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, Stride1D.Dense>>>();
});
}
}
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernel public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernel
{ {
get get
{ {
var accel = Accelerator; var accel = Accelerator;
return KernelContainer.InitOnce(ref _kernels!.SmallPrimeFactor, () => return KernelContainer.InitOnce(ref Kernels!.SmallPrimeFactor, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -143,10 +157,8 @@ public sealed class GpuKernelLease
_gpu?.Dispose(); _gpu?.Dispose();
_gpu = null; _gpu = null;
_releaseLimiter?.Invoke(); Kernels?.Dispose();
_releaseLimiter = null; GpuPrimeWorkLimiter.Release();
_kernels = null;
Pool.Enqueue(this); Pool.Enqueue(this);
} }
+75 -56
View File
@@ -1,3 +1,4 @@
using PerfectNumbers.Core;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -37,6 +38,7 @@ public sealed class KernelContainer
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder; ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SmallPrimeFactor; public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SmallPrimeFactor;
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, Stride1D.Dense>>? SpecialMax;
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length. // Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
@@ -46,6 +48,9 @@ public sealed class KernelContainer
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorExponentSlots; public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorExponentSlots;
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorCountSlot; public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorCountSlot;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorRemainingSlot; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorRemainingSlot;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SpecialMaxFactors;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SpecialMaxCandidates;
public MemoryBuffer1D<ushort, Stride1D.Dense>? SpecialMaxResult;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne; public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven; public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
@@ -67,6 +72,8 @@ public sealed class KernelContainer
} }
return current; return current;
} }
public void Dispose() => GpuKernelPool.Return(this);
} }
public readonly struct ResiduePrimeViews( public readonly struct ResiduePrimeViews(
@@ -81,44 +88,29 @@ public readonly struct ResiduePrimeViews(
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2; public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
} }
public readonly struct SmallPrimeFactorTables public readonly struct SmallPrimeFactorTables(
{
public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares;
public readonly int Count;
public SmallPrimeFactorTables(
MemoryBuffer1D<uint, Stride1D.Dense> primes, MemoryBuffer1D<uint, Stride1D.Dense> primes,
MemoryBuffer1D<ulong, Stride1D.Dense> squares, MemoryBuffer1D<ulong, Stride1D.Dense> squares,
int count) int count)
{ {
Primes = primes; public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes = primes;
Squares = squares; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares = squares;
Count = count; public readonly int Count = count;
}
public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View; public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View;
public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View; public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View;
} }
public readonly struct SmallPrimeFactorScratch public readonly struct SmallPrimeFactorScratch(
{
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> PrimeSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot;
public SmallPrimeFactorScratch(
MemoryBuffer1D<ulong, Stride1D.Dense> primeSlots, MemoryBuffer1D<ulong, Stride1D.Dense> primeSlots,
MemoryBuffer1D<int, Stride1D.Dense> exponentSlots, MemoryBuffer1D<int, Stride1D.Dense> exponentSlots,
MemoryBuffer1D<int, Stride1D.Dense> countSlot, MemoryBuffer1D<int, Stride1D.Dense> countSlot,
MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot) MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot)
{ {
PrimeSlots = primeSlots; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> PrimeSlots = primeSlots;
ExponentSlots = exponentSlots; public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots = exponentSlots;
CountSlot = countSlot; public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot = countSlot;
RemainingSlot = remainingSlot; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot = remainingSlot;
}
public void Clear() public void Clear()
{ {
@@ -129,44 +121,55 @@ public readonly struct SmallPrimeFactorScratch
} }
} }
public readonly struct SpecialMaxScratch(
MemoryBuffer1D<ulong, Stride1D.Dense> factorValues,
MemoryBuffer1D<ulong, Stride1D.Dense> candidateValues,
MemoryBuffer1D<ushort, Stride1D.Dense> resultSlot)
{
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> FactorValues = factorValues;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> CandidateValues = candidateValues;
public readonly MemoryBuffer1D<ushort, Stride1D.Dense> ResultSlot = resultSlot;
public ArrayView1D<ulong, Stride1D.Dense> FactorsView => FactorValues.View;
public ArrayView1D<ulong, Stride1D.Dense> CandidatesView => CandidateValues.View;
public ArrayView1D<ushort, Stride1D.Dense> ResultView => ResultSlot.View;
}
public class GpuKernelPool public class GpuKernelPool
{ {
private static readonly ConcurrentDictionary<Accelerator, KernelContainer> KernelCache = new(); // TODO: Replace this concurrent map with a simple accelerator-indexed lookup once kernel launchers are prewarmed during startup so we can drop the thread-safe wrapper entirely. private static readonly ConcurrentQueue<KernelContainer> KernelCache = new(); // TODO: Replace this concurrent map with a simple accelerator-indexed lookup once kernel launchers are prewarmed during startup so we can drop the thread-safe wrapper entirely.
private static KernelContainer GetKernels(Accelerator accelerator) public static KernelContainer GetKernels() => KernelCache.TryDequeue(out var container)
? container
: new KernelContainer();
public static void Return(KernelContainer container) => KernelCache.Enqueue(container);
internal static void PreloadStaticTables(KernelContainer kernels, Accelerator accelerator)
{ {
return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer()); EnsureSmallCyclesOnDevice(kernels, accelerator);
EnsureSmallPrimesOnDevice(kernels, accelerator);
EnsureSmallPrimeFactorTables(kernels, accelerator);
} }
// Ensures the small cycles table is uploaded to the device for the given accelerator. // Ensures the small cycles table is uploaded to the device for the given accelerator.
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it). // Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator) public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(KernelContainer kernels, Accelerator accelerator)
{ {
var kernels = GetKernels(accelerator);
if (kernels.SmallCycles is { } buffer) if (kernels.SmallCycles is { } buffer)
{ {
return buffer.View; return buffer.View;
} }
// Ensure single upload per accelerator even if multiple threads race here.
lock (kernels) // TODO: Remove this lock by pre-uploading the immutable small-cycle snapshot during initialization; once no mutation happens at runtime, the pool must expose a simple reference without synchronization.
{
if (kernels.SmallCycles is { } existing)
{
return existing.View;
}
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot. var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot.
var device = accelerator.Allocate1D<ulong>(host.Length); var device = accelerator.Allocate1D<ulong>(host.Length);
device.View.CopyFromCPU(host); device.View.CopyFromCPU(host);
kernels.SmallCycles = device; kernels.SmallCycles = device;
return device.View; return device.View;
} }
}
public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator) public static ResiduePrimeViews EnsureSmallPrimesOnDevice(KernelContainer kernels, Accelerator accelerator)
{ {
var kernels = GetKernels(accelerator);
if (kernels.SmallPrimesLastOne is { } lastOne && if (kernels.SmallPrimesLastOne is { } lastOne &&
kernels.SmallPrimesLastSeven is { } lastSeven && kernels.SmallPrimesLastSeven is { } lastSeven &&
kernels.SmallPrimesPow2LastOne is { } lastOnePow2 && kernels.SmallPrimesPow2LastOne is { } lastOnePow2 &&
@@ -175,8 +178,6 @@ public class GpuKernelPool
return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View); return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View);
} }
lock (kernels) // TODO: Inline these small-prime uploads into startup initialization alongside the small-cycle snapshot so we can drop runtime locking and keep the GPU pool free of synchronization.
{
if (kernels.SmallPrimesLastOne is { } existingLastOne && if (kernels.SmallPrimesLastOne is { } existingLastOne &&
kernels.SmallPrimesLastSeven is { } existingLastSeven && kernels.SmallPrimesLastSeven is { } existingLastSeven &&
kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 && kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 &&
@@ -206,18 +207,14 @@ public class GpuKernelPool
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
} }
}
public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(Accelerator accelerator) public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(KernelContainer kernels, Accelerator accelerator)
{ {
var kernels = GetKernels(accelerator);
if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer) if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
{ {
return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length); return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length);
} }
lock (kernels)
{
if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares) if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
{ {
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length); return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
@@ -236,12 +233,8 @@ public class GpuKernelPool
return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length);
} }
}
public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(Accelerator accelerator, int slotCount) public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(KernelContainer kernels, Accelerator accelerator, int slotCount)
{
var kernels = GetKernels(accelerator);
lock (kernels)
{ {
MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots; MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots;
if (primeSlots is null || primeSlots.Length < slotCount) if (primeSlots is null || primeSlots.Length < slotCount)
@@ -275,15 +268,41 @@ public class GpuKernelPool
return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!); return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!);
} }
public static SpecialMaxScratch EnsureSpecialMaxScratch(KernelContainer kernels, Accelerator accelerator, int factorCapacity)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? factorValues = kernels.SpecialMaxFactors;
if (factorValues is null || factorValues.Length < factorCapacity)
{
factorValues?.Dispose();
factorValues = accelerator.Allocate1D<ulong>(factorCapacity);
kernels.SpecialMaxFactors = factorValues;
}
MemoryBuffer1D<ulong, Stride1D.Dense>? candidateValues = kernels.SpecialMaxCandidates;
if (candidateValues is null || candidateValues.Length < factorCapacity)
{
candidateValues?.Dispose();
candidateValues = accelerator.Allocate1D<ulong>(factorCapacity);
kernels.SpecialMaxCandidates = candidateValues;
}
MemoryBuffer1D<ushort, Stride1D.Dense>? resultSlot = kernels.SpecialMaxResult;
if (resultSlot is null)
{
resultSlot = accelerator.Allocate1D<ushort>(1);
kernels.SpecialMaxResult = resultSlot;
}
return new SpecialMaxScratch(factorValues!, candidateValues!, resultSlot!);
} }
public static GpuKernelLease GetKernel(bool useGpuOrder) public static GpuKernelLease GetKernel(bool useGpuOrder)
{ {
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpu = Rent(); var gpu = Rent();
var accelerator = gpu.Accelerator; var kernels = GetKernels();
var kernels = GetKernels(accelerator); return GpuKernelLease.Rent(gpu, kernels);
return GpuKernelLease.Rent(limiter, gpu, kernels);
} }
/// <summary> /// <summary>
+12 -24
View File
@@ -1,3 +1,6 @@
using System;
using System.Threading;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public static class GpuPrimeWorkLimiter public static class GpuPrimeWorkLimiter
@@ -5,17 +8,16 @@ public static class GpuPrimeWorkLimiter
private static SemaphoreSlim _semaphore = new(1, int.MaxValue); private static SemaphoreSlim _semaphore = new(1, int.MaxValue);
private static int _currentLimit = 1; private static int _currentLimit = 1;
public static Lease Acquire() public static void Acquire() => _semaphore.Wait();
{ public static void Release() => _semaphore.Release();
// TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
// GPU jobs stop allocating Lease instances on every acquisition.
var sem = _semaphore;
// sem.Wait();
return new Lease(sem);
}
public static void SetLimit(int value) public static void SetLimit(int value)
{ {
if (value <= 0)
{
value = 1;
}
if (value == _currentLimit) if (value == _currentLimit)
{ {
return; return;
@@ -23,23 +25,9 @@ public static class GpuPrimeWorkLimiter
// TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using // TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using
// the same semaphore pool without reallocating per adjustment. // the same semaphore pool without reallocating per adjustment.
var previous = _semaphore;
_semaphore = new SemaphoreSlim(value, value); _semaphore = new SemaphoreSlim(value, value);
_currentLimit = value; _currentLimit = value;
} previous.Dispose();
public sealed class Lease
{
private readonly SemaphoreSlim _sem;
internal Lease(SemaphoreSlim sem)
{
_sem = sem;
}
public void Dispose()
{
// _sem.Release();
} }
} }
}
@@ -0,0 +1,39 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu;
internal static class GpuStaticTableInitializer
{
private static readonly ConcurrentDictionary<Accelerator, byte> Initialized = new(AcceleratorReferenceComparer.Instance);
internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator)
{
Initialized.GetOrAdd(accelerator, acc =>
{
PrimeTester.PrimeTesterGpuContextPool.EnsureStaticTables(kernels, acc);
GpuKernelPool.PreloadStaticTables(kernels, acc);
PrimeOrderGpuHeuristics.PreloadStaticTables( acc);
return 0;
});
}
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
internal static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(Accelerator obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
}
@@ -8,73 +8,57 @@ internal readonly struct HeuristicGpuDivisorTables
{ {
public static readonly HeuristicGpuDivisorTables Shared = default; public static readonly HeuristicGpuDivisorTables Shared = default;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisors;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding9;
public HeuristicGpuDivisorTables(
ArrayView1D<ulong, Stride1D.Dense> groupADivisors,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding9)
{
GroupADivisors = groupADivisors;
GroupBDivisorsEnding1 = groupBDivisorsEnding1;
GroupBDivisorsEnding7 = groupBDivisorsEnding7;
GroupBDivisorsEnding9 = groupBDivisorsEnding9;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupB(byte ending)
{
return ending switch
{
1 => GroupBDivisorsEnding1,
7 => GroupBDivisorsEnding7,
9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(HeuristicGpuDivisorTableKind kind, byte ending)
{
return kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisors,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorsEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorsEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
internal static void InitializeShared(in HeuristicGpuDivisorTables tables)
{
Unsafe.AsRef(in Shared) = tables;
}
}
internal readonly struct HeuristicGpuCombinedDivisorTables
{
public static readonly HeuristicGpuCombinedDivisorTables Shared = default;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding1; public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding3; public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding7; public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding9; public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisors;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisorSquares;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding9;
public HeuristicGpuCombinedDivisorTables( public HeuristicGpuDivisorTables(
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding1, ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding3, ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding3,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding7, ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding9) ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding9,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding1,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding3,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding7,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding9,
ArrayView1D<ulong, Stride1D.Dense> groupADivisors,
ArrayView1D<ulong, Stride1D.Dense> groupADivisorSquares,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding1,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding7,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding9,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding9)
{ {
CombinedDivisorsEnding1 = combinedDivisorsEnding1; CombinedDivisorsEnding1 = combinedDivisorsEnding1;
CombinedDivisorsEnding3 = combinedDivisorsEnding3; CombinedDivisorsEnding3 = combinedDivisorsEnding3;
CombinedDivisorsEnding7 = combinedDivisorsEnding7; CombinedDivisorsEnding7 = combinedDivisorsEnding7;
CombinedDivisorsEnding9 = combinedDivisorsEnding9; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -90,7 +74,72 @@ internal readonly struct HeuristicGpuCombinedDivisorTables
}; };
} }
internal static void InitializeShared(in HeuristicGpuCombinedDivisorTables tables) [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectCombinedSquares(byte ending)
{
return ending switch
{
1 => CombinedDivisorSquaresEnding1,
3 => CombinedDivisorSquaresEnding3,
7 => CombinedDivisorSquaresEnding7,
9 => CombinedDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupB(byte ending)
{
return ending switch
{
1 => GroupBDivisorsEnding1,
7 => GroupBDivisorsEnding7,
9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupBSquares(byte ending)
{
return ending switch
{
1 => GroupBDivisorSquaresEnding1,
7 => GroupBDivisorSquaresEnding7,
9 => GroupBDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(HeuristicGpuDivisorTableKind kind, byte ending)
{
return kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisors,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorsEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorsEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorsEnding9,
HeuristicGpuDivisorTableKind.Combined => SelectCombined(ending),
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> 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<ulong, Stride1D.Dense>.Empty,
};
}
internal static void InitializeShared(in HeuristicGpuDivisorTables tables)
{ {
Unsafe.AsRef(in Shared) = tables; Unsafe.AsRef(in Shared) = tables;
} }
@@ -0,0 +1,80 @@
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu;
internal static partial class PrimeOrderGpuHeuristics
{
internal static void EvaluateSpecialMaxCandidatesKernel(
Index1D index,
ulong phi,
ArrayView1D<ulong, Stride1D.Dense> factors,
int factorCount,
MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> candidates,
ArrayView1D<ushort, Stride1D.Dense> resultOut)
{
if (index != 0)
{
return;
}
int actual = 0;
for (int i = 0; i < factorCount; i++)
{
ulong factor = factors[i];
if (factor <= 1UL)
{
continue;
}
ulong reduced = phi / factor;
if (reduced == 0UL)
{
continue;
}
candidates[actual] = reduced;
actual++;
}
if (actual == 0)
{
resultOut[0] = 1;
return;
}
for (int i = 1; i < actual; i++)
{
ulong current = candidates[i];
int insert = i - 1;
while (insert >= 0 && candidates[insert] > current)
{
candidates[insert + 1] = candidates[insert];
insert--;
}
candidates[insert + 1] = current;
}
var divisorPartial = new GpuDivisorPartialData(divisor.Modulus);
var stepper = new ExponentRemainderStepperGpu(divisorPartial);
if (stepper.InitializeIsUnityGpu(candidates[0]))
{
resultOut[0] = 0;
return;
}
for (int i = 1; i < actual; i++)
{
if (stepper.ComputeNextIsUnityGpu(candidates[i]))
{
resultOut[0] = 0;
return;
}
}
resultOut[0] = 1;
}
}
@@ -82,25 +82,30 @@ internal static class PrimeTesterKernels
Index1D index, Index1D index,
ArrayView<int> resultFlag, ArrayView<int> resultFlag,
ulong n, ulong n,
ulong maxDivisor, ulong maxDivisorSquare,
HeuristicGpuDivisorTableKind tableKind, HeuristicGpuDivisorTableKind tableKind,
HeuristicGpuDivisorTables tables, HeuristicGpuDivisorTables tables)
HeuristicGpuCombinedDivisorTables combinedTables)
{ {
byte nMod10 = (byte)(n % 10UL); byte nMod10 = (byte)(n % 10UL);
ArrayView1D<ulong, Stride1D.Dense> divisors = tableKind == HeuristicGpuDivisorTableKind.Combined ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(tableKind, nMod10);
? combinedTables.SelectCombined(nMod10) ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(tableKind, nMod10);
: tables.SelectDivisors(tableKind, nMod10);
int divisorLength = (int)divisors.Length; int divisorLength = (int)divisorSquares.Length;
int threadIndex = index; int threadIndex = index;
bool skipThread = threadIndex >= divisorLength; if (threadIndex >= divisorLength)
ulong divisor = skipThread ? 1UL : divisors[threadIndex]; {
skipThread = skipThread | (divisor > maxDivisor); return;
}
ulong remainder = skipThread ? 1UL : n % divisor; ulong divisorSquare = divisorSquares[threadIndex];
bool hasFactor = !skipThread & (remainder == 0UL); if (divisorSquare > maxDivisorSquare)
if (hasFactor) {
return;
}
ulong divisor = divisors[threadIndex];
divisor = n % divisor;
if (divisor == 0UL)
{ {
resultFlag[0] = 1; resultFlag[0] = 1;
} }
@@ -31,11 +31,11 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
ulong step3 = ((exponent % 3UL) << 1) % 3UL; ulong step3 = ((exponent % 3UL) << 1) % 3UL;
ulong step5 = ((exponent % 5UL) << 1) % 5UL; ulong step5 = ((exponent % 5UL) << 1) % 5UL;
GpuUInt128 twoPGpu = (GpuUInt128)twoP; GpuUInt128 twoPGpu = (GpuUInt128)twoP;
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator);
ResiduePrimeViews primeViews = default; ResiduePrimeViews primeViews = default;
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(gpuLease.Kernels, accelerator);
} }
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize); var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
@@ -54,13 +54,13 @@ public class MersenneNumberLucasLehmerGpuTester
return false; return false;
} }
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
bool result; bool result;
if (exponent >= 128UL) if (exponent >= 128UL)
{ {
if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot)) if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot))
{ {
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
throw new NotSupportedException("NTT parameters for the given exponent are not available."); throw new NotSupportedException("NTT parameters for the given exponent are not available.");
} }
@@ -80,7 +80,7 @@ public class MersenneNumberLucasLehmerGpuTester
gpu.Dispose(); gpu.Dispose();
} }
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
return result; return result;
} }
@@ -29,6 +29,8 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
UInt128 remaining; UInt128 remaining;
int currentSize; int currentSize;
int found = 0; int found = 0;
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator);
while (kStart <= maxK && Volatile.Read(ref isPrime)) while (kStart <= maxK && Volatile.Read(ref isPrime))
{ {
remaining = maxK - kStart + 1UL; remaining = maxK - kStart + 1UL;
@@ -44,7 +46,6 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
// Ensure device has small cycles table for early rejections in order kernels // 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, kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
kernelArgs, foundBuffer.View, smallCyclesView); kernelArgs, foundBuffer.View, smallCyclesView);
@@ -26,7 +26,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
// GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1.
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{ {
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpuLease = RentAccelerator(); var gpuLease = RentAccelerator();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
@@ -35,14 +35,15 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetPow2ModKernel(accelerator); var kernel = GetPow2ModKernel(accelerator);
var resources = RentResources(accelerator, GpuConstants.ScanBatchSize); var resources = RentResources(accelerator, GpuConstants.ScanBatchSize);
var gpuKernels = GpuKernelPool.GetKernels();
var orderBuffer = resources.OrderBuffer; var orderBuffer = resources.OrderBuffer;
ulong[] orderArray = resources.OrderArray; ulong[] orderArray = resources.OrderArray;
int batchSize = resources.Capacity; int batchSize = resources.Capacity;
// Ensure device has small cycles and primes tables for in-kernel lookup // Ensure device has small cycles and primes tables for in-kernel lookup
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuKernels, accelerator);
ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(gpuKernels, accelerator);
UInt128 kStart = 1UL; UInt128 kStart = 1UL;
UInt128 limit = maxK + UInt128.One; UInt128 limit = maxK + UInt128.One;
@@ -109,8 +110,9 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
ReturnResources(accelerator, resources); ReturnResources(accelerator, resources);
stream.Dispose(); stream.Dispose();
// Monitor.Exit(gpuLease.ExecutionLock); // Monitor.Exit(gpuLease.ExecutionLock);
gpuKernels.Dispose();
ReturnAccelerator(gpuLease); ReturnAccelerator(gpuLease);
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
} }
private Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator) => private Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator) =>
@@ -18,6 +18,11 @@ internal enum GpuPow2ModStatus
internal static partial class PrimeOrderGpuHeuristics internal static partial class PrimeOrderGpuHeuristics
{ {
internal static void PreloadStaticTables(Accelerator accelerator)
{
_ = GetSmallPrimeDeviceCache(accelerator);
}
private static readonly ConcurrentDictionary<ulong, byte> OverflowedPrimes = new(); private static readonly ConcurrentDictionary<ulong, byte> OverflowedPrimes = new();
private static readonly ConcurrentDictionary<UInt128, byte> OverflowedPrimesWide = new(); private static readonly ConcurrentDictionary<UInt128, byte> OverflowedPrimesWide = new();
private static readonly ConcurrentDictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>> Pow2ModKernelCache = new(); private static readonly ConcurrentDictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>> Pow2ModKernelCache = new();
@@ -0,0 +1,19 @@
using System;
using System.Threading;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal static class SharedGpuContext
{
private static readonly Lazy<Context> s_context = new(() => ILGPU.Context.CreateDefault(), LazyThreadSafetyMode.ExecutionAndPublication);
private static readonly Lazy<Device> s_device = new(() => s_context.Value.GetPreferredDevice(false), LazyThreadSafetyMode.ExecutionAndPublication);
private static readonly Lazy<Accelerator> s_accelerator = new(() => s_device.Value.CreateAccelerator(s_context.Value), LazyThreadSafetyMode.ExecutionAndPublication);
internal static Context Context => s_context.Value;
internal static Device Device => s_device.Value;
internal static Accelerator Accelerator => s_accelerator.Value;
}
@@ -1,5 +1,4 @@
using Open.Numeric.Primes; using Open.Numeric.Primes;
using System;
using ILGPU.Runtime; using ILGPU.Runtime;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -225,14 +224,13 @@ public sealed class HeuristicCombinedPrimeTester
private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10)
{ {
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(nMod10, 1, PrimeTester.PrimeTesterGpuContextPool.HeuristicGpuTableScope.GroupABWithCombined); var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1);
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var kernel = gpu.HeuristicTrialDivisionKernel; var kernel = PrimeTester.PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.HeuristicTrialDivisionKernel;
var flagView1D = gpu.HeuristicFlag.View; var flagView1D = gpu.HeuristicFlag.View;
var flagView = flagView1D.AsContiguous(); var flagView = flagView1D.AsContiguous();
ulong maxDivisor = ComputeMaxDivisorLimit(maxDivisorSquare);
bool compositeDetected = false; bool compositeDetected = false;
int divisorLength = GetCombinedDivisors(nMod10).Length; int divisorLength = GetCombinedDivisors(nMod10).Length;
@@ -242,38 +240,20 @@ public sealed class HeuristicCombinedPrimeTester
divisorLength, divisorLength,
flagView, flagView,
n, n,
maxDivisor, maxDivisorSquare,
HeuristicGpuDivisorTableKind.Combined, HeuristicGpuDivisorTableKind.Combined,
gpu.HeuristicGpuTables, gpu.HeuristicGpuTables);
gpu.HeuristicCombinedTables);
accelerator.Synchronize(); accelerator.Synchronize();
flagView1D.CopyToCPU(ref compositeFlag, 1); flagView1D.CopyToCPU(ref compositeFlag, 1);
compositeDetected = compositeFlag != 0; compositeDetected = compositeFlag != 0;
gpu.Dispose(); gpu.Dispose();
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
return compositeDetected; return compositeDetected;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
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) private static bool EvaluateWithOpenNumericFallback(ulong n)
{ {
return Prime.Numbers.IsPrime(n); return Prime.Numbers.IsPrime(n);
+8 -28
View File
@@ -240,14 +240,13 @@ public sealed class HeuristicPrimeTester
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10)
{ {
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(nMod10, 1, PrimeTester.PrimeTesterGpuContextPool.HeuristicGpuTableScope.GroupAB); var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1);
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var kernel = gpu.HeuristicTrialDivisionKernel; var kernel = PrimeTester.PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.HeuristicTrialDivisionKernel;
var flagView1D = gpu.HeuristicFlag.View; var flagView1D = gpu.HeuristicFlag.View;
var flagView = flagView1D.AsContiguous(); var flagView = flagView1D.AsContiguous();
ulong maxDivisor = ComputeMaxDivisorLimit(maxDivisorSquare);
bool compositeDetected = false; bool compositeDetected = false;
int compositeFlag = 0; int compositeFlag = 0;
@@ -258,10 +257,9 @@ public sealed class HeuristicPrimeTester
groupALength, groupALength,
flagView, flagView,
n, n,
maxDivisor, maxDivisorSquare,
HeuristicGpuDivisorTableKind.GroupA, HeuristicGpuDivisorTableKind.GroupA,
gpu.HeuristicGpuTables, gpu.HeuristicGpuTables);
gpu.HeuristicCombinedTables);
accelerator.Synchronize(); accelerator.Synchronize();
flagView1D.CopyToCPU(ref compositeFlag, 1); flagView1D.CopyToCPU(ref compositeFlag, 1);
compositeDetected = compositeFlag != 0; compositeDetected = compositeFlag != 0;
@@ -293,10 +291,9 @@ public sealed class HeuristicPrimeTester
divisorLength, divisorLength,
flagView, flagView,
n, n,
maxDivisor, maxDivisorSquare,
tableKind, tableKind,
gpu.HeuristicGpuTables, gpu.HeuristicGpuTables);
gpu.HeuristicCombinedTables);
accelerator.Synchronize(); accelerator.Synchronize();
flagView1D.CopyToCPU(ref compositeFlag, 1); flagView1D.CopyToCPU(ref compositeFlag, 1);
compositeDetected = compositeFlag != 0; compositeDetected = compositeFlag != 0;
@@ -304,7 +301,7 @@ public sealed class HeuristicPrimeTester
} }
gpu.Dispose(); gpu.Dispose();
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
return compositeDetected; return compositeDetected;
} }
@@ -315,23 +312,6 @@ public sealed class HeuristicPrimeTester
{ {
return Prime.Numbers.IsPrime(n); 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) internal static ulong ComputeHeuristicDivisorSquareLimit(ulong n)
{ {
return n; return n;
+2 -2
View File
@@ -412,7 +412,7 @@ public class MersenneDivisorCycles
// Console.WriteLine($"MersenneDivisorCycles.TryFactorIntoCountsInternal hits {Volatile.Read(ref _tryFactorIntoCountsInternalHits)}"); // Console.WriteLine($"MersenneDivisorCycles.TryFactorIntoCountsInternal hits {Volatile.Read(ref _tryFactorIntoCountsInternalHits)}");
// HeuristicPrimeTester primeTester = _primeTester ??= new(); // HeuristicPrimeTester primeTester = _primeTester ??= new();
if (PrimeTester.IsPrime(remaining)) if (PrimeTester.IsPrimeGpu(remaining))
// if (PrimeTester.IsPrimeCpu(remaining, CancellationToken.None)) // if (PrimeTester.IsPrimeCpu(remaining, CancellationToken.None))
{ {
AddFactor(counts, remaining); AddFactor(counts, remaining);
@@ -943,7 +943,7 @@ public class MersenneDivisorCycles
// HeuristicPrimeTester primeTester = _primeTester ??= new(); // HeuristicPrimeTester primeTester = _primeTester ??= new();
// if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrimeCpu(divisor, CancellationToken.None)) // if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrimeCpu(divisor, CancellationToken.None))
// if (!skipPrimeOrderHeuristic && primeTester.IsPrimeGpu(divisor)) // if (!skipPrimeOrderHeuristic && primeTester.IsPrimeGpu(divisor))
if (!skipPrimeOrderHeuristic && PrimeTester.IsPrime(divisor)) if (!skipPrimeOrderHeuristic && PrimeTester.IsPrimeGpu(divisor))
{ {
ulong computedOrder = PrimeOrderCalculator.Calculate( ulong computedOrder = PrimeOrderCalculator.Calculate(
divisor, divisor,
+12 -13
View File
@@ -1,4 +1,3 @@
using System;
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
@@ -108,7 +107,7 @@ internal static partial class PrimeOrderCalculator
return candidateOrder; return candidateOrder;
} }
// private static ulong specialMaxHits;
private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
{ {
int length = factors.Count; int length = factors.Count;
@@ -121,16 +120,16 @@ internal static partial class PrimeOrderCalculator
ReadOnlySpan<FactorEntry> factorSpan = new(factors.Factors, 0, length); ReadOnlySpan<FactorEntry> factorSpan = new(factors.Factors, 0, length);
if (length <= 32)
{
Span<ulong> stackBuffer = stackalloc ulong[length]; Span<ulong> stackBuffer = stackalloc ulong[length];
if (length <= 7)
{
return EvaluateSpecialMaxCandidates(stackBuffer, factorSpan, phi, prime, divisorData); return EvaluateSpecialMaxCandidates(stackBuffer, factorSpan, phi, prime, divisorData);
} }
ulong[] rented = ThreadStaticPools.UlongPool.Rent(length); // Atomic.Add(ref specialMaxHits, 1);
bool result = EvaluateSpecialMaxCandidates(rented.AsSpan(0, length), factorSpan, phi, prime, divisorData); // Console.WriteLine($"Special max GPU hit {specialMaxHits} ({length})");
ThreadStaticPools.UlongPool.Return(rented, clearArray: false); return EvaluateSpecialMaxCandidatesGpu(stackBuffer, factorSpan, phi, prime, divisorData);
return result;
} }
private static bool EvaluateSpecialMaxCandidates(Span<ulong> buffer, ReadOnlySpan<FactorEntry> factors, ulong phi, ulong prime, in MontgomeryDivisorData divisorData) private static bool EvaluateSpecialMaxCandidates(Span<ulong> buffer, ReadOnlySpan<FactorEntry> factors, ulong phi, ulong prime, in MontgomeryDivisorData divisorData)
@@ -1194,7 +1193,7 @@ internal static partial class PrimeOrderCalculator
// Console.WriteLine($"Partial factor pending hits {Volatile.Read(ref _partialFactorPendingHits)}"); // Console.WriteLine($"Partial factor pending hits {Volatile.Read(ref _partialFactorPendingHits)}");
// bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None); // bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None);
bool isPrime = PrimeTester.IsPrime(composite); bool isPrime = PrimeTester.IsPrimeGpu(composite);
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite);
entry = entry.WithPrimality(isPrime); entry = entry.WithPrimality(isPrime);
@@ -1231,7 +1230,7 @@ internal static partial class PrimeOrderCalculator
// Atomic.Add(ref _partialFactorCofactorHits, 1UL); // Atomic.Add(ref _partialFactorCofactorHits, 1UL);
// Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}");
cofactorIsPrime = PrimeTester.IsPrime(cofactor); cofactorIsPrime = PrimeTester.IsPrimeGpu(cofactor);
// cofactorIsPrime = PrimeTester.IsPrimeGpu(cofactor); // cofactorIsPrime = PrimeTester.IsPrimeGpu(cofactor);
// cofactorIsPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(cofactor); // cofactorIsPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(cofactor);
} }
@@ -1701,7 +1700,7 @@ internal static partial class PrimeOrderCalculator
// HeuristicPrimeTester tester = _tester ??= new(); // HeuristicPrimeTester tester = _tester ??= new();
// bool isPrime = primeTester.HeuristicIsPrimeGpu(value); // bool isPrime = primeTester.HeuristicIsPrimeGpu(value);
bool isPrime = PrimeTester.IsPrime(value); bool isPrime = PrimeTester.IsPrimeGpu(value);
// bool isPrime = PrimeTester.IsPrimeGpu(value); // bool isPrime = PrimeTester.IsPrimeGpu(value);
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(value); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(value);
if (!knownComposite && isPrime) if (!knownComposite && isPrime)
@@ -1717,7 +1716,7 @@ internal static partial class PrimeOrderCalculator
// Console.WriteLine($"Factor completely after PollardRho hits {Volatile.Read(ref _factorCompletelyPollardRhoHits)}"); // Console.WriteLine($"Factor completely after PollardRho hits {Volatile.Read(ref _factorCompletelyPollardRhoHits)}");
// isPrime = primeTester.HeuristicIsPrimeGpu(factor); // isPrime = primeTester.HeuristicIsPrimeGpu(factor);
isPrime = PrimeTester.IsPrime(factor); isPrime = PrimeTester.IsPrimeGpu(factor);
// isPrime = PrimeTester.IsPrimeGpu(factor); // isPrime = PrimeTester.IsPrimeGpu(factor);
// isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factor); // isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factor);
if (isPrime) if (isPrime)
@@ -1754,7 +1753,7 @@ internal static partial class PrimeOrderCalculator
// Atomic.Add(ref _partialFactorCofactorHits, 1UL); // Atomic.Add(ref _partialFactorCofactorHits, 1UL);
// Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}");
isPrime = PrimeTester.IsPrime(quotient); isPrime = PrimeTester.IsPrimeGpu(quotient);
// isPrime = PrimeTester.IsPrimeGpu(quotient); // isPrime = PrimeTester.IsPrimeGpu(quotient);
// isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(quotient); // isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(quotient);
} }
@@ -25,8 +25,8 @@ internal static partial class PrimeOrderCalculator
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(accelerator); SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(lease.Kernels, accelerator);
SmallPrimeFactorScratch scratch = GpuKernelPool.EnsureSmallPrimeFactorScratch(accelerator, GpuSmallPrimeFactorSlots); SmallPrimeFactorScratch scratch = GpuKernelPool.EnsureSmallPrimeFactorScratch(lease.Kernels, accelerator, GpuSmallPrimeFactorSlots);
var kernel = lease.SmallPrimeFactorKernel; var kernel = lease.SmallPrimeFactorKernel;
kernel( kernel(
@@ -68,4 +68,52 @@ internal static partial class PrimeOrderCalculator
ThreadStaticPools.IntPool.Return(exponentBufferArray); ThreadStaticPools.IntPool.Return(exponentBufferArray);
return true; return true;
} }
private static bool EvaluateSpecialMaxCandidatesGpu(
Span<ulong> buffer,
ReadOnlySpan<FactorEntry> factors,
ulong phi,
ulong prime,
in MontgomeryDivisorData divisorData)
{
if (factors.Length == 0)
{
return true;
} }
int factorCount = factors.Length;
Span<ulong> factorSpan = buffer[..factorCount];
for (int i = 0; i < factorCount; i++)
{
factorSpan[i] = factors[i].Value;
}
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream;
SpecialMaxScratch scratch = GpuKernelPool.EnsureSpecialMaxScratch(lease.Kernels, accelerator, factorCapacity: factorCount);
scratch.FactorsView.SubView(0, factorCount).CopyFromCPU(ref MemoryMarshal.GetReference(factorSpan), factorCount);
var kernel = lease.SpecialMaxKernel;
kernel(
stream,
1,
phi,
scratch.FactorsView,
factorCount,
divisorData,
scratch.CandidatesView,
scratch.ResultView);
stream.Synchronize();
ushort result = 0;
scratch.ResultView.CopyToCPU(ref result, 1);
lease.Dispose();
return result != 0;
}
}
+287 -410
View File
@@ -93,50 +93,43 @@ public sealed class PrimeTester
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPrimeGpu(ulong n) public static bool IsPrimeGpu(ulong n)
{ {
// bool belowGpuRange = n < 31UL; GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTesterGpuContextPool.Rent(1);
var accelerator = gpu.Accelerator;
var kernel = PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.Kernel;
// if (belowGpuRange) ulong value = n;
// { byte flag = 0;
return IsPrime(n);
// }
// var limiter = GpuPrimeWorkLimiter.Acquire(); var input = gpu.Input;
// var gpu = PrimeTesterGpuContextPool.Rent(1); var output = gpu.Output;
// var accelerator = gpu.Accelerator; var inputView = input.View;
// var kernel = gpu.Kernel; var outputView = output.View;
// ulong value = n; inputView.CopyFromCPU(ref value, 1);
// byte flag = 0; kernel(
1,
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();
outputView.CopyToCPU(ref flag, 1);
// var input = gpu.Input; gpu.Dispose();
// var output = gpu.Output; GpuPrimeWorkLimiter.Release();
// var inputView = input.View;
// var outputView = output.View;
// inputView.CopyFromCPU(ref value, 1); return flag != 0;
// kernel(
// 1,
// 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();
// outputView.CopyToCPU(ref flag, 1);
// gpu.Dispose();
// limiter.Dispose();
// return flag != 0;
} }
public static int GpuBatchSize { get; set; } = 262_144; public static int GpuBatchSize { get; set; } = 262_144;
@@ -159,26 +152,7 @@ public sealed class PrimeTester
return; return;
} }
int toWarm = target - WarmedGpuLeaseCount; PrimeTesterGpuContextPool.WarmUp(target, GpuBatchSize);
var leases = new PrimeTesterGpuContextPool.PrimeTesterGpuContextLease[toWarm];
ReadOnlySpan<byte> supportedDigits = PrimeTesterGpuContextPool.SupportedDigits;
int digitIndex = 0;
for (int i = 0; i < toWarm; i++)
{
byte trailingDigit = supportedDigits[digitIndex];
leases[i] = PrimeTesterGpuContextPool.Rent(trailingDigit, GpuBatchSize);
digitIndex++;
if (digitIndex == supportedDigits.Length)
{
digitIndex = 0;
}
}
for (int i = 0; i < toWarm; i++)
{
leases[i].Dispose();
}
WarmedGpuLeaseCount = target; WarmedGpuLeaseCount = target;
} }
} }
@@ -186,45 +160,25 @@ public sealed class PrimeTester
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsPrimeBatchGpu(ReadOnlySpan<ulong> values, Span<byte> results) public static void IsPrimeBatchGpu(ReadOnlySpan<ulong> values, Span<byte> results)
{ {
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTesterGpuContextPool.Rent(GpuBatchSize);
var accelerator = gpu.Accelerator;
var kernel = PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.Kernel;
int totalLength = values.Length; int totalLength = values.Length;
int batchSize = GpuBatchSize; int batchSize = GpuBatchSize;
int pos = 0;
while (pos < totalLength)
{
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 input = gpu.Input;
var output = gpu.Output; var output = gpu.Output;
var inputView = input.View; var inputView = input.View;
var outputView = output.View; var outputView = output.View;
int blockStart = pos; int pos = 0;
while (blockStart < totalLength) while (pos < totalLength)
{ {
byte blockDigit = (byte)(values[blockStart] % 10UL); int remaining = totalLength - pos;
if (blockDigit != trailingDigit) int count = remaining > batchSize ? batchSize : remaining;
{
break;
}
int limit = Math.Min(batchSize, totalLength - blockStart); var valueSlice = values.Slice(pos, count);
int count = 0;
while (count < limit && (byte)(values[blockStart + count] % 10UL) == trailingDigit)
{
count++;
}
if (count == 0)
{
break;
}
var valueSlice = values.Slice(blockStart, count);
ref ulong valueRef = ref MemoryMarshal.GetReference(valueSlice); ref ulong valueRef = ref MemoryMarshal.GetReference(valueSlice);
inputView.CopyFromCPU(ref valueRef, count); inputView.CopyFromCPU(ref valueRef, count);
@@ -243,77 +197,94 @@ public sealed class PrimeTester
gpu.DevicePrimesPow2LastNine.View, gpu.DevicePrimesPow2LastNine.View,
outputView); outputView);
accelerator.Synchronize(); accelerator.Synchronize();
var resultSlice = results.Slice(blockStart, count); var resultSlice = results.Slice(pos, count);
ref byte resultRef = ref MemoryMarshal.GetReference(resultSlice); ref byte resultRef = ref MemoryMarshal.GetReference(resultSlice);
outputView.CopyToCPU(ref resultRef, count); outputView.CopyToCPU(ref resultRef, count);
blockStart += count; pos += count;
} }
gpu.Dispose(); gpu.Dispose();
pos = blockStart; GpuPrimeWorkLimiter.Release();
}
limiter.Dispose();
} }
internal static class PrimeTesterGpuContextPool internal static class PrimeTesterGpuContextPool
{ {
internal enum HeuristicGpuTableScope : byte private static readonly ConcurrentQueue<PrimeTesterGpuContextLease> Pool = new();
{ private static readonly object WarmUpLock = new();
GroupAB = 0, private static int WarmedLeaseCount;
GroupABWithCombined = 1,
}
private static readonly byte[] SupportedTrailingDigits = [1, 3, 7, 9];
private const int ScopeCount = 2;
private static readonly ConcurrentQueue<PrimeTesterGpuContextLease>[,] Pools;
private static readonly object SharedTablesLock = new(); private static readonly object SharedTablesLock = new();
private static readonly Dictionary<string, SharedKernelTables> SharedTables = new(); private static readonly Dictionary<Accelerator, SharedHeuristicGpuTables> SharedTables = new(AcceleratorReferenceComparer.Instance);
private static readonly Dictionary<(string Key, byte Digit, HeuristicGpuTableScope Scope), TrailingDigitTables> DigitTables = new();
static PrimeTesterGpuContextPool() private static SharedHeuristicGpuTables RentSharedTables(Accelerator accelerator)
{ {
Pools = new ConcurrentQueue<PrimeTesterGpuContextLease>[SupportedTrailingDigits.Length, ScopeCount]; lock (SharedTablesLock)
for (int digitIndex = 0; digitIndex < SupportedTrailingDigits.Length; digitIndex++)
{ {
for (int scopeIndex = 0; scopeIndex < ScopeCount; scopeIndex++) if (!SharedTables.TryGetValue(accelerator, out var tables))
{ {
Pools[digitIndex, scopeIndex] = new ConcurrentQueue<PrimeTesterGpuContextLease>(); tables = new SharedHeuristicGpuTables(accelerator);
SharedTables.Add(accelerator, tables);
} }
tables.AddReference();
return tables;
} }
} }
internal static ReadOnlySpan<byte> SupportedDigits => SupportedTrailingDigits; private static void ReleaseSharedTables(SharedHeuristicGpuTables tables)
private static int GetPoolIndex(byte trailingDigit)
{ {
return trailingDigit switch if (tables is null)
{ {
1 => 0, return;
3 => 1,
7 => 2,
9 => 3,
_ => throw new ArgumentOutOfRangeException(nameof(trailingDigit), trailingDigit, "Only trailing digits 1, 3, 7, and 9 are supported."),
};
} }
private static int GetScopeIndex(HeuristicGpuTableScope scope) lock (SharedTablesLock)
{ {
return scope switch tables.Release();
{ }
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) internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator)
{ {
var pool = Pools[GetPoolIndex(trailingDigit), GetScopeIndex(tableScope)]; var tables = RentSharedTables(accelerator);
if (!pool.TryDequeue(out var lease)) var heuristicTables = tables.CreateHeuristicDivisorTables();
HeuristicGpuDivisorTables.InitializeShared(in heuristicTables);
ReleaseSharedTables(tables);
}
internal static void WarmUp(int target, int minBufferCapacity)
{ {
lease = new PrimeTesterGpuContextLease(trailingDigit, minBufferCapacity, tableScope); if (target <= 0)
{
return;
}
lock (WarmUpLock)
{
if (target <= WarmedLeaseCount)
{
return;
}
KernelContainer kernels = GpuKernelPool.GetKernels();
GpuStaticTableInitializer.EnsureStaticTables(kernels, SharedGpuContext.Accelerator);
int toCreate = target - WarmedLeaseCount;
for (int i = 0; i < toCreate; i++)
{
Pool.Enqueue(new PrimeTesterGpuContextLease(minBufferCapacity));
}
kernels.Dispose();
WarmedLeaseCount = target;
}
}
internal static PrimeTesterGpuContextLease Rent(int minBufferCapacity = 1)
{
if (!Pool.TryDequeue(out var lease))
{
lease = new PrimeTesterGpuContextLease(minBufferCapacity);
} }
else else
{ {
@@ -324,42 +295,33 @@ public sealed class PrimeTester
return lease; return lease;
} }
internal static void Return(PrimeTesterGpuContextLease lease) internal static void Return(PrimeTesterGpuContextLease lease)
{ {
lease.Accelerator.Synchronize(); lease.Accelerator.Synchronize();
Pools[GetPoolIndex(lease.TrailingDigit), GetScopeIndex(lease.TableScope)].Enqueue(lease); Pool.Enqueue(lease);
} }
internal static void DisposeAll() internal static void DisposeAll()
{ {
for (int digitIndex = 0; digitIndex < SupportedTrailingDigits.Length; digitIndex++) while (Pool.TryDequeue(out var lease))
{
for (int scopeIndex = 0; scopeIndex < ScopeCount; scopeIndex++)
{
var pool = Pools[digitIndex, scopeIndex];
while (pool.TryDequeue(out var lease))
{ {
lease.DisposeResources(); lease.DisposeResources();
} }
}
}
lock (SharedTablesLock) lock (SharedTablesLock)
{ {
foreach (var entry in DigitTables.Values)
{
entry.Dispose();
}
DigitTables.Clear();
foreach (var entry in SharedTables.Values) foreach (var entry in SharedTables.Values)
{ {
entry.Dispose(); entry.Dispose();
} }
SharedTables.Clear(); SharedTables.Clear();
var emptyTables = default(HeuristicGpuDivisorTables);
HeuristicGpuDivisorTables.InitializeShared(in emptyTables);
} }
WarmedLeaseCount = 0;
} }
internal static void Clear(Accelerator accelerator) internal static void Clear(Accelerator accelerator)
@@ -369,13 +331,10 @@ public sealed class PrimeTester
return; return;
} }
lock (WarmUpLock)
{
var retained = new List<PrimeTesterGpuContextLease>(); var retained = new List<PrimeTesterGpuContextLease>();
for (int digitIndex = 0; digitIndex < SupportedTrailingDigits.Length; digitIndex++) while (Pool.TryDequeue(out var lease))
{
for (int scopeIndex = 0; scopeIndex < ScopeCount; scopeIndex++)
{
var pool = Pools[digitIndex, scopeIndex];
while (pool.TryDequeue(out var lease))
{ {
if (lease.Accelerator == accelerator) if (lease.Accelerator == accelerator)
{ {
@@ -390,172 +349,46 @@ public sealed class PrimeTester
int retainedCount = retained.Count; int retainedCount = retained.Count;
for (int i = 0; i < retainedCount; i++) for (int i = 0; i < retainedCount; i++)
{ {
pool.Enqueue(retained[i]); Pool.Enqueue(retained[i]);
} }
retained.Clear(); WarmedLeaseCount = Pool.Count;
}
} }
lock (SharedTablesLock) lock (SharedTablesLock)
{ {
string key = CreateAcceleratorKey(accelerator); if (SharedTables.Remove(accelerator, out var tables))
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(); tables.Dispose();
} }
}
}
private static TrailingDigitTables RentTrailingDigitTables(Accelerator accelerator, byte trailingDigit, HeuristicGpuTableScope scope) if (SharedTables.Count == 0)
{ {
string key = CreateAcceleratorKey(accelerator); var emptyTables = default(HeuristicGpuDivisorTables);
var compositeKey = (key, trailingDigit, scope); HeuristicGpuDivisorTables.InitializeShared(in emptyTables);
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 internal sealed class PrimeTesterGpuContextLease : IDisposable
{ {
private bool _returned; private bool _returned;
private readonly SharedKernelTables _sharedTables; private readonly SharedHeuristicGpuTables _sharedTables;
private readonly TrailingDigitTables _digitTables;
private readonly HeuristicGpuDivisorTables _heuristicDivisorTables; private readonly HeuristicGpuDivisorTables _heuristicDivisorTables;
private readonly HeuristicGpuCombinedDivisorTables _heuristicCombinedTables;
internal readonly Context AcceleratorContext; internal readonly Context AcceleratorContext;
public readonly Accelerator Accelerator; public readonly Accelerator Accelerator;
public readonly byte TrailingDigit;
public readonly HeuristicGpuTableScope TableScope; public static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> Kernel = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel);
public readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> Kernel;
public readonly Action<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables, HeuristicGpuCombinedDivisorTables> HeuristicTrialDivisionKernel; public static readonly Action<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables> HeuristicTrialDivisionKernel = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel);
public MemoryBuffer1D<ulong, Stride1D.Dense> Input = null!; public MemoryBuffer1D<ulong, Stride1D.Dense> Input = null!;
public MemoryBuffer1D<byte, Stride1D.Dense> Output = null!; public MemoryBuffer1D<byte, Stride1D.Dense> Output = null!;
public MemoryBuffer1D<int, Stride1D.Dense> HeuristicFlag = null!; public MemoryBuffer1D<int, Stride1D.Dense> HeuristicFlag = null!;
public int BufferCapacity; public int BufferCapacity;
internal PrimeTesterGpuContextLease(byte trailingDigit, int minBufferCapacity, HeuristicGpuTableScope tableScope)
{
TrailingDigit = trailingDigit;
TableScope = tableScope;
AcceleratorContext = Context.CreateDefault();
Accelerator = AcceleratorContext.GetPreferredDevice(false).CreateAccelerator(AcceleratorContext);
Kernel = Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel);
HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables, HeuristicGpuCombinedDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel);
_sharedTables = RentSharedTables(Accelerator);
_digitTables = RentTrailingDigitTables(Accelerator, trailingDigit, tableScope);
_heuristicDivisorTables = new HeuristicGpuDivisorTables(
_sharedTables.HeuristicGroupADivisors.View,
_digitTables.GroupBDivisorsEnding1View,
_digitTables.GroupBDivisorsEnding7View,
_digitTables.GroupBDivisorsEnding9View);
HeuristicGpuDivisorTables.InitializeShared(in _heuristicDivisorTables);
if (_digitTables.HasCombinedTables)
{
_heuristicCombinedTables = new HeuristicGpuCombinedDivisorTables(
_digitTables.CombinedDivisorsEnding1View,
_digitTables.CombinedDivisorsEnding3View,
_digitTables.CombinedDivisorsEnding7View,
_digitTables.CombinedDivisorsEnding9View);
HeuristicGpuCombinedDivisorTables.InitializeShared(in _heuristicCombinedTables);
}
else
{
_heuristicCombinedTables = HeuristicGpuCombinedDivisorTables.Shared;
}
EnsureCapacity(minBufferCapacity);
_returned = false;
}
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault => _sharedTables.DevicePrimesDefault; public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault => _sharedTables.DevicePrimesDefault;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne => _sharedTables.DevicePrimesLastOne; public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne => _sharedTables.DevicePrimesLastOne;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven => _sharedTables.DevicePrimesLastSeven; public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven => _sharedTables.DevicePrimesLastSeven;
@@ -566,11 +399,43 @@ public sealed class PrimeTester
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven => _sharedTables.DevicePrimesPow2LastSeven; public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven => _sharedTables.DevicePrimesPow2LastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree => _sharedTables.DevicePrimesPow2LastThree; public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree => _sharedTables.DevicePrimesPow2LastThree;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine => _sharedTables.DevicePrimesPow2LastNine; public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine => _sharedTables.DevicePrimesPow2LastNine;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors => _sharedTables.HeuristicGroupADivisors;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares => _sharedTables.HeuristicGroupADivisorSquares;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1 => _sharedTables.HeuristicGroupBDivisorsEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1 => _sharedTables.HeuristicGroupBDivisorSquaresEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7 => _sharedTables.HeuristicGroupBDivisorsEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7 => _sharedTables.HeuristicGroupBDivisorSquaresEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9 => _sharedTables.HeuristicGroupBDivisorsEnding9;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9 => _sharedTables.HeuristicGroupBDivisorSquaresEnding9;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding1 => _sharedTables.HeuristicCombinedDivisorsEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding1 => _sharedTables.HeuristicCombinedDivisorSquaresEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding3 => _sharedTables.HeuristicCombinedDivisorsEnding3;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding3 => _sharedTables.HeuristicCombinedDivisorSquaresEnding3;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding7 => _sharedTables.HeuristicCombinedDivisorsEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding7 => _sharedTables.HeuristicCombinedDivisorSquaresEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9 => _sharedTables.HeuristicCombinedDivisorsEnding9;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding9 => _sharedTables.HeuristicCombinedDivisorSquaresEnding9;
public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables; public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables;
public HeuristicGpuCombinedDivisorTables HeuristicCombinedTables => _heuristicCombinedTables;
internal void EnsureCapacity(int minCapacity) internal PrimeTesterGpuContextLease(int minBufferCapacity)
{
AcceleratorContext = SharedGpuContext.Context;
Accelerator = SharedGpuContext.Accelerator;
// GpuStaticTableInitializer.EnsureStaticTables(Accelerator);
// Kernel = Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel);
// HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel);
_sharedTables = RentSharedTables(Accelerator);
_heuristicDivisorTables = _sharedTables.CreateHeuristicDivisorTables();
HeuristicGpuDivisorTables.InitializeShared(in _heuristicDivisorTables);
EnsureCapacity(minBufferCapacity);
_returned = false;
}
public void EnsureCapacity(int minCapacity)
{ {
if (minCapacity <= BufferCapacity) if (minCapacity <= BufferCapacity)
{ {
@@ -599,7 +464,7 @@ public sealed class PrimeTester
} }
_returned = true; _returned = true;
PrimeTesterGpuContextPool.Return(this); Return(this);
} }
internal void DisposeResources() internal void DisposeResources()
@@ -607,19 +472,34 @@ public sealed class PrimeTester
Input?.Dispose(); Input?.Dispose();
Output?.Dispose(); Output?.Dispose();
HeuristicFlag?.Dispose(); HeuristicFlag?.Dispose();
ReleaseTrailingDigitTables(_digitTables);
ReleaseSharedTables(_sharedTables); ReleaseSharedTables(_sharedTables);
Accelerator.Dispose(); // These resources are shared between GPU leases
AcceleratorContext.Dispose(); // Accelerator.Dispose();
// AcceleratorContext.Dispose();
} }
} }
private sealed class SharedKernelTables
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
internal static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(Accelerator obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
private sealed class SharedHeuristicGpuTables
{ {
private int _referenceCount; private int _referenceCount;
internal SharedKernelTables(Accelerator accelerator, string key) internal SharedHeuristicGpuTables(Accelerator accelerator)
{ {
Key = key;
var primesDefault = DivisorGenerator.SmallPrimes; var primesDefault = DivisorGenerator.SmallPrimes;
DevicePrimesDefault = accelerator.Allocate1D<uint>(primesDefault.Length); DevicePrimesDefault = accelerator.Allocate1D<uint>(primesDefault.Length);
@@ -662,10 +542,44 @@ public sealed class PrimeTester
DevicePrimesPow2LastNine.View.CopyFromCPU(primesPow2LastNine); DevicePrimesPow2LastNine.View.CopyFromCPU(primesPow2LastNine);
HeuristicPrimeSieves.EnsureInitialized(); HeuristicPrimeSieves.EnsureInitialized();
HeuristicGroupADivisors = CopySpanToDevice(accelerator, HeuristicPrimeSieves.GroupADivisors); 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);
} }
internal string Key { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault { get; } internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne { get; } internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven { get; } internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven { get; }
@@ -677,122 +591,54 @@ public sealed class PrimeTester
internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree { get; } internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine { get; } internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors { get; } internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding3 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding3 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding9 { get; }
internal HeuristicGpuDivisorTables CreateHeuristicDivisorTables()
{
return new HeuristicGpuDivisorTables(
HeuristicCombinedDivisorsEnding1.View,
HeuristicCombinedDivisorsEnding3.View,
HeuristicCombinedDivisorsEnding7.View,
HeuristicCombinedDivisorsEnding9.View,
HeuristicCombinedDivisorSquaresEnding1.View,
HeuristicCombinedDivisorSquaresEnding3.View,
HeuristicCombinedDivisorSquaresEnding7.View,
HeuristicCombinedDivisorSquaresEnding9.View,
HeuristicGroupADivisors.View,
HeuristicGroupADivisorSquares.View,
HeuristicGroupBDivisorsEnding1.View,
HeuristicGroupBDivisorSquaresEnding1.View,
HeuristicGroupBDivisorsEnding7.View,
HeuristicGroupBDivisorSquaresEnding7.View,
HeuristicGroupBDivisorsEnding9.View,
HeuristicGroupBDivisorSquaresEnding9.View);
}
internal void AddReference() internal void AddReference()
{ {
_referenceCount++; _referenceCount++;
} }
internal bool Release() internal void Release()
{ {
if (_referenceCount > 0) if (_referenceCount > 0)
{ {
_referenceCount--; _referenceCount--;
} }
return _referenceCount == 0;
}
internal void Dispose()
{
DevicePrimesDefault.Dispose();
DevicePrimesLastOne.Dispose();
DevicePrimesLastSeven.Dispose();
DevicePrimesLastThree.Dispose();
DevicePrimesLastNine.Dispose();
DevicePrimesPow2Default.Dispose();
DevicePrimesPow2LastOne.Dispose();
DevicePrimesPow2LastSeven.Dispose();
DevicePrimesPow2LastThree.Dispose();
DevicePrimesPow2LastNine.Dispose();
HeuristicGroupADivisors.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<ulong, Stride1D.Dense>? CombinedDivisorsEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense>? CombinedDivisorsEnding3 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense>? CombinedDivisorsEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense>? CombinedDivisorsEnding9 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense>? GroupBDivisorsEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense>? GroupBDivisorsEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense>? GroupBDivisorsEnding9 { get; }
internal ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding1View => CombinedDivisorsEnding1?.View ?? ArrayView1D<ulong, Stride1D.Dense>.Empty;
internal ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding3View => CombinedDivisorsEnding3?.View ?? ArrayView1D<ulong, Stride1D.Dense>.Empty;
internal ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding7View => CombinedDivisorsEnding7?.View ?? ArrayView1D<ulong, Stride1D.Dense>.Empty;
internal ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding9View => CombinedDivisorsEnding9?.View ?? ArrayView1D<ulong, Stride1D.Dense>.Empty;
internal ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding1View => GroupBDivisorsEnding1?.View ?? ArrayView1D<ulong, Stride1D.Dense>.Empty;
internal ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding7View => GroupBDivisorsEnding7?.View ?? ArrayView1D<ulong, Stride1D.Dense>.Empty;
internal ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding9View => GroupBDivisorsEnding9?.View ?? ArrayView1D<ulong, Stride1D.Dense>.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<ulong, Stride1D.Dense> CopySpanToDevice(Accelerator accelerator, ReadOnlySpan<ulong> span) private static MemoryBuffer1D<ulong, Stride1D.Dense> CopySpanToDevice(Accelerator accelerator, ReadOnlySpan<ulong> span)
@@ -800,8 +646,8 @@ public sealed class PrimeTester
var buffer = accelerator.Allocate1D<ulong>(span.Length); var buffer = accelerator.Allocate1D<ulong>(span.Length);
if (!span.IsEmpty) if (!span.IsEmpty)
{ {
ref ulong source = ref MemoryMarshal.GetReference(span); ref ulong sourceRef = ref MemoryMarshal.GetReference(span);
buffer.View.CopyFromCPU(ref source, span.Length); buffer.View.CopyFromCPU(ref sourceRef, span.Length);
} }
return buffer; return buffer;
@@ -823,6 +669,37 @@ public sealed class PrimeTester
return buffer; return buffer;
} }
internal void Dispose()
{
DevicePrimesDefault.Dispose();
DevicePrimesLastOne.Dispose();
DevicePrimesLastSeven.Dispose();
DevicePrimesLastThree.Dispose();
DevicePrimesLastNine.Dispose();
DevicePrimesPow2Default.Dispose();
DevicePrimesPow2LastOne.Dispose();
DevicePrimesPow2LastSeven.Dispose();
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();
}
}
} }
// Expose cache clearing for accelerator disposal coordination // Expose cache clearing for accelerator disposal coordination
@@ -855,7 +732,7 @@ public sealed class PrimeTester
// TODO: Route this batch helper through the shared GPU kernel pool from // TODO: Route this batch helper through the shared GPU kernel pool from
// GpuUInt128BinaryGcdBenchmarks so we reuse cached kernels, pinned host buffers, // GpuUInt128BinaryGcdBenchmarks so we reuse cached kernels, pinned host buffers,
// and divisor-cycle staging instead of allocating new device buffers per call. // and divisor-cycle staging instead of allocating new device buffers per call.
var gpu = PrimeTesterGpuContextPool.Rent(1); var gpu = PrimeTesterGpuContextPool.Rent();
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SharesFactorKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SharesFactorKernel);
-8
View File
@@ -1,8 +0,0 @@
# 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.