Merge remote-tracking branch 'github/codex/optimize-evenperfectbitscanner-for-static-arrays-khd9w8' into work

# Conflicts:
#	PerfectNumbers.Core/Gpu/GpuContextPool.cs
#	PerfectNumbers.Core/Gpu/GpuKernelLease.cs
#	PerfectNumbers.Core/Gpu/GpuKernelPool.cs
#	PerfectNumbers.Core/Gpu/GpuStaticTableInitializer.cs
#	PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs
#	PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs
#	PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs
#	PerfectNumbers.Core/PrimeTester.cs
This commit is contained in:
Marek Lemańczyk committed 2025-11-03 23:59:12 +01:00
1 parent 507daaddb9
commit 4c4d4243fb
23 files changed
+856 -631

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;
+3 -3
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;
@@ -176,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)
@@ -626,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();
} }
}); });
} }
+9 -11
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;
@@ -15,14 +14,12 @@ public static class GpuContextPool
public readonly Context Context; public readonly Context Context;
public readonly Accelerator Accelerator; public readonly Accelerator Accelerator;
private static readonly Context _sharedContext = Context.CreateDefault(); public PooledContext(KernelContainer kernels)
private static readonly Device _sharedDevice = _sharedContext.GetPreferredDevice(false);
private static readonly Accelerator _sharedAccelerator = _sharedDevice.CreateAccelerator(_sharedContext);
public PooledContext()
{ {
Context = _sharedContext; Context = SharedGpuContext.Context;
Accelerator = _sharedAccelerator; Accelerator = SharedGpuContext.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
@@ -48,7 +45,7 @@ public static class GpuContextPool
private static int WarmedGpuContextCount; private static int WarmedGpuContextCount;
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu. // Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
public static GpuContextLease Rent() public static GpuContextLease Rent()
{ {
if (GpuPool.TryDequeue(out var gpu)) if (GpuPool.TryDequeue(out var gpu))
{ {
@@ -56,7 +53,7 @@ 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()));
} }
@@ -74,7 +71,8 @@ public static class GpuContextPool
PooledContext context; PooledContext context;
for (int i = 0; i < toCreate; i++) for (int i = 0; i < toCreate; i++)
{ {
context = new PooledContext(); KernelContainer kernels = GpuKernelPool.GetKernels();
context = new PooledContext(kernels);
contexts[i] = context; contexts[i] = context;
pool.Enqueue(context); pool.Enqueue(context);
} }
+31 -19
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);
} }
+263 -244
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;
@@ -7,297 +8,315 @@ namespace PerfectNumbers.Core.Gpu;
public readonly struct ResidueAutomatonArgs public readonly struct ResidueAutomatonArgs
{ {
public readonly ulong Q0M10; public readonly ulong Q0M10;
public readonly ulong Step10; public readonly ulong Step10;
public readonly ulong Q0M8; public readonly ulong Q0M8;
public readonly ulong Step8; public readonly ulong Step8;
public readonly ulong Q0M3; public readonly ulong Q0M3;
public readonly ulong Step3; public readonly ulong Step3;
public readonly ulong Q0M5; public readonly ulong Q0M5;
public readonly ulong Step5; public readonly ulong Step5;
public ResidueAutomatonArgs(ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5) public ResidueAutomatonArgs(ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5)
{ {
Q0M10 = q0m10; Step10 = step10; Q0M8 = q0m8; Step8 = step8; Q0M3 = q0m3; Step3 = step3; Q0M5 = q0m5; Step5 = step5; Q0M10 = q0m10; Step10 = step10; Q0M8 = q0m8; Step8 = step8; Q0M3 = q0m3; Step3 = step3; Q0M5 = q0m5; Step5 = step5;
} }
} }
public sealed class KernelContainer public sealed class KernelContainer
{ {
// Serializes first-time initialization of kernels/buffers per accelerator. // Serializes first-time initialization of kernels/buffers per accelerator.
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order; public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental; ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod; ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
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>>? IncrementalOrder; ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
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;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes; public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorPrimeSlots; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorPrimeSlots;
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<uint, Stride1D.Dense>? SmallPrimesLastOne; public MemoryBuffer1D<ulong, Stride1D.Dense>? SpecialMaxFactors;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne; public MemoryBuffer1D<ulong, Stride1D.Dense>? SpecialMaxCandidates;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven; public MemoryBuffer1D<ushort, Stride1D.Dense>? SpecialMaxResult;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven; public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
{ {
var current = Volatile.Read(ref slot); var current = Volatile.Read(ref slot);
if (current is not null) if (current is not null)
{ {
return current; return current;
} }
current = Volatile.Read(ref slot); current = Volatile.Read(ref slot);
if (current is null) if (current is null)
{ {
current = factory(); current = factory();
Volatile.Write(ref slot, current); Volatile.Write(ref slot, current);
} }
return current; return current;
} }
public void Dispose() => GpuKernelPool.Return(this);
} }
public readonly struct ResiduePrimeViews( public readonly struct ResiduePrimeViews(
ArrayView1D<uint, Stride1D.Dense> lastOne, ArrayView1D<uint, Stride1D.Dense> lastOne,
ArrayView1D<uint, Stride1D.Dense> lastSeven, ArrayView1D<uint, Stride1D.Dense> lastSeven,
ArrayView1D<ulong, Stride1D.Dense> lastOnePow2, ArrayView1D<ulong, Stride1D.Dense> lastOnePow2,
ArrayView1D<ulong, Stride1D.Dense> lastSevenPow2) ArrayView1D<ulong, Stride1D.Dense> lastSevenPow2)
{ {
public readonly ArrayView1D<uint, Stride1D.Dense> LastOne = lastOne; public readonly ArrayView1D<uint, Stride1D.Dense> LastOne = lastOne;
public readonly ArrayView1D<uint, Stride1D.Dense> LastSeven = lastSeven; public readonly ArrayView1D<uint, Stride1D.Dense> LastSeven = lastSeven;
public readonly ArrayView1D<ulong, Stride1D.Dense> LastOnePow2 = lastOnePow2; public readonly ArrayView1D<ulong, Stride1D.Dense> LastOnePow2 = lastOnePow2;
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2; public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
} }
public readonly struct SmallPrimeFactorTables public readonly struct SmallPrimeFactorTables(
MemoryBuffer1D<uint, Stride1D.Dense> primes,
MemoryBuffer1D<ulong, Stride1D.Dense> squares,
int count)
{ {
public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes; public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes = primes;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares = squares;
public readonly int Count; public readonly int Count = count;
public SmallPrimeFactorTables( public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View;
MemoryBuffer1D<uint, Stride1D.Dense> primes, public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View;
MemoryBuffer1D<ulong, Stride1D.Dense> squares,
int count)
{
Primes = primes;
Squares = squares;
Count = count;
}
public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View;
public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View;
} }
public readonly struct SmallPrimeFactorScratch public readonly struct SmallPrimeFactorScratch(
MemoryBuffer1D<ulong, Stride1D.Dense> primeSlots,
MemoryBuffer1D<int, Stride1D.Dense> exponentSlots,
MemoryBuffer1D<int, Stride1D.Dense> countSlot,
MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot)
{ {
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> PrimeSlots; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> PrimeSlots = primeSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots; public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots = exponentSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot; public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot = countSlot;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot = remainingSlot;
public SmallPrimeFactorScratch( public void Clear()
MemoryBuffer1D<ulong, Stride1D.Dense> primeSlots, {
MemoryBuffer1D<int, Stride1D.Dense> exponentSlots, PrimeSlots.MemSetToZero();
MemoryBuffer1D<int, Stride1D.Dense> countSlot, ExponentSlots.MemSetToZero();
MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot) CountSlot.MemSetToZero();
{ RemainingSlot.MemSetToZero();
PrimeSlots = primeSlots; }
ExponentSlots = exponentSlots; }
CountSlot = countSlot;
RemainingSlot = remainingSlot;
}
public void Clear() public readonly struct SpecialMaxScratch(
{ MemoryBuffer1D<ulong, Stride1D.Dense> factorValues,
PrimeSlots.MemSetToZero(); MemoryBuffer1D<ulong, Stride1D.Dense> candidateValues,
ExponentSlots.MemSetToZero(); MemoryBuffer1D<ushort, Stride1D.Dense> resultSlot)
CountSlot.MemSetToZero(); {
RemainingSlot.MemSetToZero(); 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
return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer()); : new KernelContainer();
}
// Ensures the small cycles table is uploaded to the device for the given accelerator. public static void Return(KernelContainer container) => KernelCache.Enqueue(container);
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator)
{
var kernels = GetKernels(accelerator);
if (kernels.SmallCycles is { } buffer)
{
return buffer.View;
}
// Ensure single upload per accelerator even if multiple threads race here. internal static void PreloadStaticTables(KernelContainer kernels, Accelerator accelerator)
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. {
{ EnsureSmallCyclesOnDevice(kernels, accelerator);
if (kernels.SmallCycles is { } existing) EnsureSmallPrimesOnDevice(kernels, accelerator);
{ EnsureSmallPrimeFactorTables(kernels, accelerator);
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. // Ensures the small cycles table is uploaded to the device for the given accelerator.
var device = accelerator.Allocate1D<ulong>(host.Length); // Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
device.View.CopyFromCPU(host); public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(KernelContainer kernels, Accelerator accelerator)
kernels.SmallCycles = device; {
return device.View; if (kernels.SmallCycles is { } buffer)
} {
} return buffer.View;
}
public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator) 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 kernels = GetKernels(accelerator); device.View.CopyFromCPU(host);
if (kernels.SmallPrimesLastOne is { } lastOne && kernels.SmallCycles = device;
kernels.SmallPrimesLastSeven is { } lastSeven && return device.View;
kernels.SmallPrimesPow2LastOne is { } lastOnePow2 && }
kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2)
{
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. public static ResiduePrimeViews EnsureSmallPrimesOnDevice(KernelContainer kernels, Accelerator accelerator)
{ {
if (kernels.SmallPrimesLastOne is { } existingLastOne && if (kernels.SmallPrimesLastOne is { } lastOne &&
kernels.SmallPrimesLastSeven is { } existingLastSeven && kernels.SmallPrimesLastSeven is { } lastSeven &&
kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 && kernels.SmallPrimesPow2LastOne is { } lastOnePow2 &&
kernels.SmallPrimesPow2LastSeven is { } existingLastSevenPow2) kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2)
{ {
return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View); return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View);
} }
var hostLastOne = PrimesGenerator.SmallPrimesLastOne; if (kernels.SmallPrimesLastOne is { } existingLastOne &&
var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven; kernels.SmallPrimesLastSeven is { } existingLastSeven &&
var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 &&
var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; kernels.SmallPrimesPow2LastSeven is { } existingLastSevenPow2)
{
return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View);
}
var deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length); var hostLastOne = PrimesGenerator.SmallPrimesLastOne;
deviceLastOne.View.CopyFromCPU(hostLastOne); var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven;
var deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length); var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
deviceLastSeven.View.CopyFromCPU(hostLastSeven); var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
var deviceLastOnePow2 = accelerator.Allocate1D<ulong>(hostLastOnePow2.Length);
deviceLastOnePow2.View.CopyFromCPU(hostLastOnePow2);
var deviceLastSevenPow2 = accelerator.Allocate1D<ulong>(hostLastSevenPow2.Length);
deviceLastSevenPow2.View.CopyFromCPU(hostLastSevenPow2);
kernels.SmallPrimesLastOne = deviceLastOne; var deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length);
kernels.SmallPrimesLastSeven = deviceLastSeven; deviceLastOne.View.CopyFromCPU(hostLastOne);
kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; var deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length);
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; deviceLastSeven.View.CopyFromCPU(hostLastSeven);
var deviceLastOnePow2 = accelerator.Allocate1D<ulong>(hostLastOnePow2.Length);
deviceLastOnePow2.View.CopyFromCPU(hostLastOnePow2);
var deviceLastSevenPow2 = accelerator.Allocate1D<ulong>(hostLastSevenPow2.Length);
deviceLastSevenPow2.View.CopyFromCPU(hostLastSevenPow2);
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); kernels.SmallPrimesLastOne = deviceLastOne;
} kernels.SmallPrimesLastSeven = deviceLastSeven;
} kernels.SmallPrimesPow2LastOne = deviceLastOnePow2;
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(Accelerator accelerator) return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
{ }
var kernels = GetKernels(accelerator);
if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
{
return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length);
}
lock (kernels) public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(KernelContainer kernels, Accelerator accelerator)
{ {
if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares) if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
{ {
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length); return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length);
} }
var hostPrimes = PrimesGenerator.SmallPrimes; if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
var hostSquares = PrimesGenerator.SmallPrimesPow2; {
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
}
var devicePrimes = accelerator.Allocate1D<uint>(hostPrimes.Length); var hostPrimes = PrimesGenerator.SmallPrimes;
devicePrimes.View.CopyFromCPU(hostPrimes); var hostSquares = PrimesGenerator.SmallPrimesPow2;
var deviceSquares = accelerator.Allocate1D<ulong>(hostSquares.Length);
deviceSquares.View.CopyFromCPU(hostSquares);
kernels.SmallPrimeFactorsPrimes = devicePrimes; var devicePrimes = accelerator.Allocate1D<uint>(hostPrimes.Length);
kernels.SmallPrimeFactorsSquares = deviceSquares; devicePrimes.View.CopyFromCPU(hostPrimes);
var deviceSquares = accelerator.Allocate1D<ulong>(hostSquares.Length);
deviceSquares.View.CopyFromCPU(hostSquares);
return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); kernels.SmallPrimeFactorsPrimes = devicePrimes;
} kernels.SmallPrimeFactorsSquares = deviceSquares;
}
public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(Accelerator accelerator, int slotCount) return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length);
{ }
var kernels = GetKernels(accelerator);
lock (kernels)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots;
if (primeSlots is null || primeSlots.Length < slotCount)
{
primeSlots?.Dispose();
primeSlots = accelerator.Allocate1D<ulong>(slotCount);
kernels.SmallPrimeFactorPrimeSlots = primeSlots;
}
MemoryBuffer1D<int, Stride1D.Dense>? exponentSlots = kernels.SmallPrimeFactorExponentSlots; public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(KernelContainer kernels, Accelerator accelerator, int slotCount)
if (exponentSlots is null || exponentSlots.Length < slotCount) {
{ MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots;
exponentSlots?.Dispose(); if (primeSlots is null || primeSlots.Length < slotCount)
exponentSlots = accelerator.Allocate1D<int>(slotCount); {
kernels.SmallPrimeFactorExponentSlots = exponentSlots; primeSlots?.Dispose();
} primeSlots = accelerator.Allocate1D<ulong>(slotCount);
kernels.SmallPrimeFactorPrimeSlots = primeSlots;
}
MemoryBuffer1D<int, Stride1D.Dense>? countSlot = kernels.SmallPrimeFactorCountSlot; MemoryBuffer1D<int, Stride1D.Dense>? exponentSlots = kernels.SmallPrimeFactorExponentSlots;
if (countSlot is null) if (exponentSlots is null || exponentSlots.Length < slotCount)
{ {
countSlot = accelerator.Allocate1D<int>(1); exponentSlots?.Dispose();
kernels.SmallPrimeFactorCountSlot = countSlot; exponentSlots = accelerator.Allocate1D<int>(slotCount);
} kernels.SmallPrimeFactorExponentSlots = exponentSlots;
}
MemoryBuffer1D<ulong, Stride1D.Dense>? remainingSlot = kernels.SmallPrimeFactorRemainingSlot; MemoryBuffer1D<int, Stride1D.Dense>? countSlot = kernels.SmallPrimeFactorCountSlot;
if (remainingSlot is null) if (countSlot is null)
{ {
remainingSlot = accelerator.Allocate1D<ulong>(1); countSlot = accelerator.Allocate1D<int>(1);
kernels.SmallPrimeFactorRemainingSlot = remainingSlot; kernels.SmallPrimeFactorCountSlot = countSlot;
} }
return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!); MemoryBuffer1D<ulong, Stride1D.Dense>? remainingSlot = kernels.SmallPrimeFactorRemainingSlot;
} if (remainingSlot is null)
} {
remainingSlot = accelerator.Allocate1D<ulong>(1);
kernels.SmallPrimeFactorRemainingSlot = remainingSlot;
}
public static GpuKernelLease GetKernel(bool useGpuOrder) return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!);
{ }
var limiter = GpuPrimeWorkLimiter.Acquire();
var gpu = Rent();
var accelerator = gpu.Accelerator;
var kernels = GetKernels(accelerator);
return GpuKernelLease.Rent(limiter, gpu, kernels);
}
/// <summary> public static SpecialMaxScratch EnsureSpecialMaxScratch(KernelContainer kernels, Accelerator accelerator, int factorCapacity)
/// Runs a GPU action with an acquired accelerator and stream. {
/// </summary> MemoryBuffer1D<ulong, Stride1D.Dense>? factorValues = kernels.SpecialMaxFactors;
/// <param name="action">Action to run with (Accelerator, Stream).</param> if (factorValues is null || factorValues.Length < factorCapacity)
public static void Run(Action<Accelerator, AcceleratorStream> action) {
{ factorValues?.Dispose();
var lease = GetKernel(useGpuOrder: true); factorValues = accelerator.Allocate1D<ulong>(factorCapacity);
var accelerator = lease.Accelerator; kernels.SpecialMaxFactors = factorValues;
var stream = accelerator.CreateStream(); }
action(accelerator, stream);
stream.Dispose();
lease.Dispose();
} 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)
{
GpuPrimeWorkLimiter.Acquire();
var gpu = Rent();
var kernels = GetKernels();
return GpuKernelLease.Rent(gpu, kernels);
}
/// <summary>
/// Runs a GPU action with an acquired accelerator and stream.
/// </summary>
/// <param name="action">Action to run with (Accelerator, Stream).</param>
public static void Run(Action<Accelerator, AcceleratorStream> action)
{
var lease = GetKernel(useGpuOrder: true);
var accelerator = lease.Accelerator;
var stream = accelerator.CreateStream();
action(accelerator, stream);
stream.Dispose();
lease.Dispose();
}
} }
+3 -24
View File
@@ -8,16 +8,10 @@ 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) if (value <= 0)
{ {
@@ -36,19 +30,4 @@ public static class GpuPrimeWorkLimiter
_currentLimit = value; _currentLimit = value;
previous.Dispose(); previous.Dispose();
} }
public sealed class Lease : IDisposable
{
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);
}
}
}
@@ -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;
}
}
@@ -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);
@@ -121,7 +121,7 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
} }
ArrayPool<ulong>.Shared.Return(orderArray); ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose(); orderBuffer.Dispose();
gpuLease.Dispose(); gpuLease.Dispose();
} }
} }
@@ -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;
} }
@@ -4,62 +4,63 @@ namespace PerfectNumbers.Core.Gpu;
public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuOrder) public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuOrder)
{ {
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder; private readonly bool _useGpuOrder = useGpuOrder;
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 gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
UInt128 kStart = UInt128.One; UInt128 kStart = UInt128.One;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
var kernel = _kernelType switch var kernel = _kernelType switch
{ {
GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel, GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel,
_ => gpuLease.IncrementalOrderKernel, _ => gpuLease.IncrementalOrderKernel,
}; };
var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run. var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
UInt128 remaining; UInt128 remaining;
int currentSize; int currentSize;
int found = 0; int found = 0;
while (kStart <= maxK && Volatile.Read(ref isPrime))
{
remaining = maxK - kStart + 1UL;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
foundBuffer.MemSetToZero();
// Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + 1UL;
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
// MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the
// single snapshot block instead of recalculating mod 10/8/5/3 in every iteration.
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
// Ensure device has small cycles table for early rejections in order kernels var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator);
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); while (kStart <= maxK && Volatile.Read(ref isPrime))
kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul, {
kernelArgs, foundBuffer.View, smallCyclesView); remaining = maxK - kStart + 1UL;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
foundBuffer.MemSetToZero();
// Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + 1UL;
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
// MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the
// single snapshot block instead of recalculating mod 10/8/5/3 in every iteration.
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
stream.Synchronize(); // Ensure device has small cycles table for early rejections in order kernels
foundBuffer.View.CopyToCPU(ref found, 1); kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
if (found != 0) kernelArgs, foundBuffer.View, smallCyclesView);
{
Volatile.Write(ref isPrime, false);
break;
}
kStart += (UInt128)currentSize; stream.Synchronize();
} foundBuffer.View.CopyToCPU(ref found, 1);
if (found != 0)
{
Volatile.Write(ref isPrime, false);
break;
}
foundBuffer.Dispose(); kStart += (UInt128)currentSize;
gpuLease.Dispose(); }
}
foundBuffer.Dispose();
gpuLease.Dispose();
}
} }
@@ -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;
@@ -34,15 +34,16 @@ 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;
@@ -108,9 +109,10 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
ReturnResources(accelerator, resources); ReturnResources(accelerator, resources);
stream.Dispose(); stream.Dispose();
// Monitor.Exit(gpuLease.ExecutionLock); // Monitor.Exit(gpuLease.ExecutionLock);
ReturnAccelerator(gpuLease); gpuKernels.Dispose();
limiter.Dispose(); ReturnAccelerator(gpuLease);
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;
@@ -42,20 +41,20 @@ public sealed class HeuristicCombinedPrimeTester
} }
public static void EnsureInitialized() public static void EnsureInitialized()
{ {
// Intentionally left blank. Accessing this method forces the static constructor to run. // Intentionally left blank. Accessing this method forces the static constructor to run.
} }
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding1Span => CombinedDivisorsEnding1; internal static ReadOnlySpan<ulong> CombinedDivisorsEnding1Span => CombinedDivisorsEnding1;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding3Span => CombinedDivisorsEnding3; internal static ReadOnlySpan<ulong> CombinedDivisorsEnding3Span => CombinedDivisorsEnding3;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding7Span => CombinedDivisorsEnding7; internal static ReadOnlySpan<ulong> CombinedDivisorsEnding7Span => CombinedDivisorsEnding7;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding9Span => CombinedDivisorsEnding9; internal static ReadOnlySpan<ulong> CombinedDivisorsEnding9Span => CombinedDivisorsEnding9;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding1SquaresSpan => CombinedDivisorsEnding1Squares; internal static ReadOnlySpan<ulong> CombinedDivisorsEnding1SquaresSpan => CombinedDivisorsEnding1Squares;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding3SquaresSpan => CombinedDivisorsEnding3Squares; internal static ReadOnlySpan<ulong> CombinedDivisorsEnding3SquaresSpan => CombinedDivisorsEnding3Squares;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding7SquaresSpan => CombinedDivisorsEnding7Squares; internal static ReadOnlySpan<ulong> CombinedDivisorsEnding7SquaresSpan => CombinedDivisorsEnding7Squares;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding9SquaresSpan => CombinedDivisorsEnding9Squares; internal static ReadOnlySpan<ulong> CombinedDivisorsEnding9SquaresSpan => CombinedDivisorsEnding9Squares;
private const ulong Wheel210 = 210UL; private const ulong Wheel210 = 210UL;
@@ -223,39 +222,39 @@ 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(1); 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();
bool compositeDetected = false; bool compositeDetected = false;
int divisorLength = GetCombinedDivisors(nMod10).Length; int divisorLength = GetCombinedDivisors(nMod10).Length;
int compositeFlag = 0; int compositeFlag = 0;
flagView1D.CopyFromCPU(ref compositeFlag, 1); flagView1D.CopyFromCPU(ref compositeFlag, 1);
kernel( kernel(
divisorLength, divisorLength,
flagView, flagView,
n, n,
maxDivisorSquare, maxDivisorSquare,
HeuristicGpuDivisorTableKind.Combined, HeuristicGpuDivisorTableKind.Combined,
gpu.HeuristicGpuTables); gpu.HeuristicGpuTables);
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 bool EvaluateWithOpenNumericFallback(ulong n) private static bool EvaluateWithOpenNumericFallback(ulong n)
{ {
return Prime.Numbers.IsPrime(n); return Prime.Numbers.IsPrime(n);
} }
+62 -62
View File
@@ -213,7 +213,7 @@ public sealed class HeuristicPrimeTester
int processed = 0; int processed = 0;
while (processed < interleaveBatchSize) while (processed < interleaveBatchSize)
{ {
if (!TrySelectNextGroupBDivisor(endingOrder, indices, maxDivisorSquare, out ulong divisor)) if (!TrySelectNextGroupBDivisor(endingOrder, indices, maxDivisorSquare, out ulong divisor))
{ {
groupBHasMore = false; groupBHasMore = false;
break; break;
@@ -238,77 +238,77 @@ 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(1); 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();
bool compositeDetected = false; bool compositeDetected = false;
int compositeFlag = 0; int compositeFlag = 0;
int groupALength = HeuristicPrimeSieves.GroupADivisors.Length; int groupALength = HeuristicPrimeSieves.GroupADivisors.Length;
flagView1D.CopyFromCPU(ref compositeFlag, 1); flagView1D.CopyFromCPU(ref compositeFlag, 1);
kernel( kernel(
groupALength, groupALength,
flagView, flagView,
n, n,
maxDivisorSquare, maxDivisorSquare,
HeuristicGpuDivisorTableKind.GroupA, HeuristicGpuDivisorTableKind.GroupA,
gpu.HeuristicGpuTables); gpu.HeuristicGpuTables);
accelerator.Synchronize(); accelerator.Synchronize();
flagView1D.CopyToCPU(ref compositeFlag, 1); flagView1D.CopyToCPU(ref compositeFlag, 1);
compositeDetected = compositeFlag != 0; compositeDetected = compositeFlag != 0;
if (!compositeDetected) if (!compositeDetected)
{ {
ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10); ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10);
for (int i = 0; i < endingOrder.Length && !compositeDetected; i++) for (int i = 0; i < endingOrder.Length && !compositeDetected; i++)
{ {
byte ending = endingOrder[i]; byte ending = endingOrder[i];
HeuristicGpuDivisorTableKind tableKind = ending switch HeuristicGpuDivisorTableKind tableKind = ending switch
{ {
1 => HeuristicGpuDivisorTableKind.GroupBEnding1, 1 => HeuristicGpuDivisorTableKind.GroupBEnding1,
7 => HeuristicGpuDivisorTableKind.GroupBEnding7, 7 => HeuristicGpuDivisorTableKind.GroupBEnding7,
9 => HeuristicGpuDivisorTableKind.GroupBEnding9, 9 => HeuristicGpuDivisorTableKind.GroupBEnding9,
_ => HeuristicGpuDivisorTableKind.GroupA, _ => HeuristicGpuDivisorTableKind.GroupA,
}; };
if (tableKind == HeuristicGpuDivisorTableKind.GroupA) if (tableKind == HeuristicGpuDivisorTableKind.GroupA)
{ {
continue; continue;
} }
int divisorLength = GetGroupBDivisors(ending).Length; int divisorLength = GetGroupBDivisors(ending).Length;
compositeFlag = 0; compositeFlag = 0;
flagView1D.CopyFromCPU(ref compositeFlag, 1); flagView1D.CopyFromCPU(ref compositeFlag, 1);
kernel( kernel(
divisorLength, divisorLength,
flagView, flagView,
n, n,
maxDivisorSquare, maxDivisorSquare,
tableKind, tableKind,
gpu.HeuristicGpuTables); gpu.HeuristicGpuTables);
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 bool EvaluateWithOpenNumericFallback(ulong n) private static bool EvaluateWithOpenNumericFallback(ulong n)
{ {
return Prime.Numbers.IsPrime(n); return Prime.Numbers.IsPrime(n);
} }
@@ -327,7 +327,7 @@ public sealed class HeuristicPrimeTester
}; };
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TrySelectNextGroupBDivisor(ReadOnlySpan<byte> endingOrder, Span<int> indices, ulong maxDivisorSquare, out ulong divisor) private static bool TrySelectNextGroupBDivisor(ReadOnlySpan<byte> endingOrder, Span<int> indices, ulong maxDivisorSquare, out ulong divisor)
{ {
ulong bestCandidate = ulong.MaxValue; ulong bestCandidate = ulong.MaxValue;
int bestEndingIndex = -1; int bestEndingIndex = -1;
+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];
if (length <= 7)
{ {
Span<ulong> stackBuffer = stackalloc ulong[length];
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);
} }
+96 -48
View File
@@ -9,63 +9,111 @@ namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator internal static partial class PrimeOrderCalculator
{ {
private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2; private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2;
private const int GpuSmallPrimeFactorSlots = 64; private const int GpuSmallPrimeFactorSlots = 64;
private static bool TryPopulateSmallPrimeFactorsGpu(ulong value, uint limit, Dictionary<ulong, int> counts, out int factorCount, out ulong remaining) private static bool TryPopulateSmallPrimeFactorsGpu(ulong value, uint limit, Dictionary<ulong, int> counts, out int factorCount, out ulong remaining)
{ {
var primeBufferArray = ThreadStaticPools.UlongPool.Rent(GpuSmallPrimeFactorSlots); var primeBufferArray = ThreadStaticPools.UlongPool.Rent(GpuSmallPrimeFactorSlots);
var exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots); var exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots);
Span<ulong> primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); Span<ulong> primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
Span<int> exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); Span<int> exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
remaining = value; remaining = value;
var lease = GpuKernelPool.GetKernel(useGpuOrder: true); var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
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(
stream, stream,
1, 1,
value, value,
limit, limit,
tables.PrimesView, tables.PrimesView,
tables.SquaresView, tables.SquaresView,
tables.Count, tables.Count,
scratch.PrimeSlots.View, scratch.PrimeSlots.View,
scratch.ExponentSlots.View, scratch.ExponentSlots.View,
scratch.CountSlot.View, scratch.CountSlot.View,
scratch.RemainingSlot.View); scratch.RemainingSlot.View);
stream.Synchronize(); stream.Synchronize();
factorCount = 0; factorCount = 0;
scratch.CountSlot.View.CopyToCPU(ref factorCount, 1); scratch.CountSlot.View.CopyToCPU(ref factorCount, 1);
factorCount = Math.Min(factorCount, GpuSmallPrimeFactorSlots); factorCount = Math.Min(factorCount, GpuSmallPrimeFactorSlots);
if (factorCount > 0) if (factorCount > 0)
{ {
scratch.PrimeSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(primeBuffer), factorCount); scratch.PrimeSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(primeBuffer), factorCount);
scratch.ExponentSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentBuffer), factorCount); scratch.ExponentSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentBuffer), factorCount);
} }
scratch.RemainingSlot.View.CopyToCPU(ref remaining, 1); scratch.RemainingSlot.View.CopyToCPU(ref remaining, 1);
for (int i = 0; i < factorCount; i++) for (int i = 0; i < factorCount; i++)
{ {
ulong primeValue = primeBuffer[i]; ulong primeValue = primeBuffer[i];
int exponent = exponentBuffer[i]; int exponent = exponentBuffer[i];
counts.Add(primeValue, exponent); counts.Add(primeValue, exponent);
} }
lease.Dispose(); lease.Dispose();
ThreadStaticPools.UlongPool.Return(primeBufferArray); ThreadStaticPools.UlongPool.Return(primeBufferArray);
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;
}
} }
+114 -82
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;
@@ -167,10 +160,10 @@ 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 gpu = PrimeTesterGpuContextPool.Rent(GpuBatchSize);
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var kernel = gpu.Kernel; var kernel = PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.Kernel;
int totalLength = values.Length; int totalLength = values.Length;
int batchSize = GpuBatchSize; int batchSize = GpuBatchSize;
@@ -212,7 +205,7 @@ public sealed class PrimeTester
} }
gpu.Dispose(); gpu.Dispose();
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
} }
internal static class PrimeTesterGpuContextPool internal static class PrimeTesterGpuContextPool
@@ -221,23 +214,16 @@ public sealed class PrimeTester
private static readonly object WarmUpLock = new(); private static readonly object WarmUpLock = new();
private static int WarmedLeaseCount; private static int WarmedLeaseCount;
private static readonly object SharedTablesLock = new(); private static readonly object SharedTablesLock = new();
private static readonly Dictionary<string, SharedHeuristicGpuTables> SharedTables = new(); private static readonly Dictionary<Accelerator, SharedHeuristicGpuTables> SharedTables = new(AcceleratorReferenceComparer.Instance);
private static string CreateAcceleratorKey(Accelerator accelerator)
{
return accelerator.AcceleratorType + ":" + accelerator.Name;
}
private static SharedHeuristicGpuTables RentSharedTables(Accelerator accelerator) private static SharedHeuristicGpuTables RentSharedTables(Accelerator accelerator)
{ {
string key = CreateAcceleratorKey(accelerator);
lock (SharedTablesLock) lock (SharedTablesLock)
{ {
if (!SharedTables.TryGetValue(key, out var tables)) if (!SharedTables.TryGetValue(accelerator, out var tables))
{ {
tables = new SharedHeuristicGpuTables(accelerator, key); tables = new SharedHeuristicGpuTables(accelerator);
SharedTables.Add(key, tables); SharedTables.Add(accelerator, tables);
} }
tables.AddReference(); tables.AddReference();
@@ -258,6 +244,14 @@ public sealed class PrimeTester
} }
} }
internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator)
{
var tables = RentSharedTables(accelerator);
var heuristicTables = tables.CreateHeuristicDivisorTables();
HeuristicGpuDivisorTables.InitializeShared(in heuristicTables);
ReleaseSharedTables(tables);
}
internal static void WarmUp(int target, int minBufferCapacity) internal static void WarmUp(int target, int minBufferCapacity)
{ {
if (target <= 0) if (target <= 0)
@@ -272,12 +266,16 @@ public sealed class PrimeTester
return; return;
} }
KernelContainer kernels = GpuKernelPool.GetKernels();
GpuStaticTableInitializer.EnsureStaticTables(kernels, SharedGpuContext.Accelerator);
int toCreate = target - WarmedLeaseCount; int toCreate = target - WarmedLeaseCount;
for (int i = 0; i < toCreate; i++) for (int i = 0; i < toCreate; i++)
{ {
Pool.Enqueue(new PrimeTesterGpuContextLease(minBufferCapacity)); Pool.Enqueue(new PrimeTesterGpuContextLease(minBufferCapacity));
} }
kernels.Dispose();
WarmedLeaseCount = target; WarmedLeaseCount = target;
} }
} }
@@ -319,6 +317,8 @@ public sealed class PrimeTester
} }
SharedTables.Clear(); SharedTables.Clear();
var emptyTables = default(HeuristicGpuDivisorTables);
HeuristicGpuDivisorTables.InitializeShared(in emptyTables);
} }
WarmedLeaseCount = 0; WarmedLeaseCount = 0;
@@ -354,6 +354,20 @@ public sealed class PrimeTester
WarmedLeaseCount = Pool.Count; WarmedLeaseCount = Pool.Count;
} }
lock (SharedTablesLock)
{
if (SharedTables.Remove(accelerator, out var tables))
{
tables.Dispose();
}
if (SharedTables.Count == 0)
{
var emptyTables = default(HeuristicGpuDivisorTables);
HeuristicGpuDivisorTables.InitializeShared(in emptyTables);
}
}
} }
internal sealed class PrimeTesterGpuContextLease : IDisposable internal sealed class PrimeTesterGpuContextLease : IDisposable
@@ -365,8 +379,11 @@ public sealed class PrimeTester
internal readonly Context AcceleratorContext; internal readonly Context AcceleratorContext;
public readonly Accelerator Accelerator; public readonly Accelerator Accelerator;
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> HeuristicTrialDivisionKernel; 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 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!;
@@ -401,36 +418,17 @@ public sealed class PrimeTester
public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables; public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables;
private static readonly Context _sharedContext = Context.CreateDefault();
private static readonly Device _sharedDevice = _sharedContext.GetPreferredDevice(false);
private static readonly Accelerator _sharedAccelerator = _sharedDevice.CreateAccelerator(_sharedContext);
internal PrimeTesterGpuContextLease(int minBufferCapacity) internal PrimeTesterGpuContextLease(int minBufferCapacity)
{ {
AcceleratorContext = _sharedContext;// Context.CreateDefault(); AcceleratorContext = SharedGpuContext.Context;
Accelerator = _sharedAccelerator; //AcceleratorContext.GetPreferredDevice(false).CreateAccelerator(AcceleratorContext); Accelerator = SharedGpuContext.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); // GpuStaticTableInitializer.EnsureStaticTables(Accelerator);
HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel); // 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); _sharedTables = RentSharedTables(Accelerator);
_heuristicDivisorTables = new HeuristicGpuDivisorTables( _heuristicDivisorTables = _sharedTables.CreateHeuristicDivisorTables();
_sharedTables.HeuristicCombinedDivisorsEnding1.View,
_sharedTables.HeuristicCombinedDivisorsEnding3.View,
_sharedTables.HeuristicCombinedDivisorsEnding7.View,
_sharedTables.HeuristicCombinedDivisorsEnding9.View,
_sharedTables.HeuristicCombinedDivisorSquaresEnding1.View,
_sharedTables.HeuristicCombinedDivisorSquaresEnding3.View,
_sharedTables.HeuristicCombinedDivisorSquaresEnding7.View,
_sharedTables.HeuristicCombinedDivisorSquaresEnding9.View,
_sharedTables.HeuristicGroupADivisors.View,
_sharedTables.HeuristicGroupADivisorSquares.View,
_sharedTables.HeuristicGroupBDivisorsEnding1.View,
_sharedTables.HeuristicGroupBDivisorSquaresEnding1.View,
_sharedTables.HeuristicGroupBDivisorsEnding7.View,
_sharedTables.HeuristicGroupBDivisorSquaresEnding7.View,
_sharedTables.HeuristicGroupBDivisorsEnding9.View,
_sharedTables.HeuristicGroupBDivisorSquaresEnding9.View);
HeuristicGpuDivisorTables.InitializeShared(in _heuristicDivisorTables); HeuristicGpuDivisorTables.InitializeShared(in _heuristicDivisorTables);
EnsureCapacity(minBufferCapacity); EnsureCapacity(minBufferCapacity);
@@ -481,13 +479,27 @@ public sealed class PrimeTester
} }
} }
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 sealed class SharedHeuristicGpuTables
{ {
private int _referenceCount; private int _referenceCount;
internal SharedHeuristicGpuTables(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);
@@ -568,7 +580,6 @@ public sealed class PrimeTester
HeuristicCombinedDivisorSquaresEnding9 = CopySpanToDevice(accelerator, combinedSquaresEnding9); 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; }
@@ -596,6 +607,27 @@ public sealed class PrimeTester
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9 { get; } internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding9 { 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++;