ConcurrentDictionary removed. Kernel made dependent on the shared GPU context.

This commit is contained in:
mlemanczyk committed 2025-11-07 10:16:33 +01:00
1 parent 9537addf38
commit 119690086a
11 files changed
+343 -315

No files matched your search

@@ -349,7 +349,7 @@ public class GpuKernelCompilationTests
private static void CompileGpuKernelPoolKernels(Accelerator accelerator) private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
{ {
_ = accelerator; _ = accelerator;
var lease = GpuKernelPool.GetKernel(); var lease = GpuKernelPool.Rent();
try try
{ {
_ = lease.OrderKernel; _ = lease.OrderKernel;
@@ -363,7 +363,7 @@ public class GpuKernelCompilationTests
lease.Dispose(); lease.Dispose();
} }
var orderLease = GpuKernelPool.GetKernel(); var orderLease = GpuKernelPool.Rent();
try try
{ {
_ = orderLease.OrderKernel; _ = orderLease.OrderKernel;
+300 -264
View File
@@ -12,324 +12,360 @@ namespace PerfectNumbers.Core;
public sealed class DivisorCycleCache public sealed class DivisorCycleCache
{ {
private const byte ByteZero = 0; private const byte ByteZero = 0;
private const byte ByteOne = 1; private const byte ByteOne = 1;
private const int StackBufferThreshold = 128; private const int StackBufferThreshold = 128;
private readonly ConcurrentDictionary<Accelerator, Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> _gpuKernel = LoadKernel(SharedGpuContext.Accelerator);
private volatile ulong[] _snapshot; private volatile ulong[] _snapshot;
private volatile bool _useGpuGeneration = true; private volatile bool _useGpuGeneration = true;
private readonly int _divisorCyclesBatchSize; private readonly int _divisorCyclesBatchSize;
private static int _sharedDivisorCyclesBatchSize = GpuConstants.GpuCycleStepsPerInvocation; private static int _sharedDivisorCyclesBatchSize = GpuConstants.GpuCycleStepsPerInvocation;
private const int CycleCacheTrackingLimit = 500_000; private const int CycleCacheTrackingLimit = 500_000;
private static readonly HashSet<ulong> CycleCacheTrackedDivisors = new(); private static readonly HashSet<ulong> CycleCacheTrackedDivisors = new();
private static readonly object CycleCacheTrackingLock = new(); private static readonly object CycleCacheTrackingLock = new();
private static ulong CycleCacheHitCount; private static ulong CycleCacheHitCount;
private static bool CycleCacheTrackingDisabled; private static bool CycleCacheTrackingDisabled;
public static void SetDivisorCyclesBatchSize(int divisorCyclesBatchSize) public static void SetDivisorCyclesBatchSize(int divisorCyclesBatchSize)
{ {
_sharedDivisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize); _sharedDivisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
} }
public static DivisorCycleCache Shared { get; } = new DivisorCycleCache(_sharedDivisorCyclesBatchSize); public static DivisorCycleCache Shared { get; } = new DivisorCycleCache(_sharedDivisorCyclesBatchSize);
public int PreferredBatchSize => _divisorCyclesBatchSize; public int PreferredBatchSize => _divisorCyclesBatchSize;
private DivisorCycleCache(int divisorCyclesBatchSize) private DivisorCycleCache(int divisorCyclesBatchSize)
{ {
_divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize); _divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); _snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
} }
public void ConfigureGeneratorDevice(bool useGpu) public void ConfigureGeneratorDevice(bool useGpu)
{ {
_useGpuGeneration = useGpu; _useGpuGeneration = useGpu;
} }
public void RefreshSnapshot() public void RefreshSnapshot()
{ {
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); _snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
} }
public ulong GetCycleLength(ulong divisor, bool skipPrimeOrderHeuristic = false) public ulong GetCycleLength(ulong divisor, bool skipPrimeOrderHeuristic = false)
{ {
if (!skipPrimeOrderHeuristic) if (!skipPrimeOrderHeuristic)
{ {
Span<ulong> result = stackalloc ulong[1]; Span<ulong> result = stackalloc ulong[1];
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor }; ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
GetCycleLengths(singleDivisor, result); GetCycleLengths(singleDivisor, result);
return result[0]; return result[0];
} }
// The skipPrimeOrderHeuristic path is only used for divisors produced by the CPU by-divisor scanner, // The skipPrimeOrderHeuristic path is only used for divisors produced by the CPU by-divisor scanner,
// which always generates values greater than one. Keep this guard disabled here to avoid repeating the // which always generates values greater than one. Keep this guard disabled here to avoid repeating the
// validation already performed by the general GetCycleLengths path. // validation already performed by the general GetCycleLengths path.
// if (divisor <= 1UL) // if (divisor <= 1UL)
// { // {
// throw new InvalidDataException("Divisor must be > 1"); // throw new InvalidDataException("Divisor must be > 1");
// } // }
ulong[] snapshot = _snapshot; ulong[] snapshot = _snapshot;
if (divisor < (ulong)snapshot.Length) if (divisor < (ulong)snapshot.Length)
{ {
ulong cached = snapshot[divisor]; ulong cached = snapshot[divisor];
if (cached == 0UL) if (cached == 0UL)
{ {
throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
} }
// ObserveCycleCacheDivisor(divisor); // ObserveCycleCacheDivisor(divisor);
return cached; return cached;
} }
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: true); return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: true);
} }
public void GetCycleLengths(ReadOnlySpan<ulong> divisors, Span<ulong> cycles) public void GetCycleLengths(ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
{ {
ulong[] snapshot = _snapshot; ulong[] snapshot = _snapshot;
int length = divisors.Length; int length = divisors.Length;
int[]? rentedMissing = null; int[]? rentedMissing = null;
Span<int> missingBuffer = length <= StackBufferThreshold Span<int> missingBuffer = length <= StackBufferThreshold
? stackalloc int[length] ? stackalloc int[length]
: new Span<int>(rentedMissing = ThreadStaticPools.IntPool.Rent(length), 0, length); : new Span<int>(rentedMissing = ThreadStaticPools.IntPool.Rent(length), 0, length);
int missingCount = 0; int missingCount = 0;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
ulong divisor = divisors[i]; ulong divisor = divisors[i];
if (divisor <= 1UL) if (divisor <= 1UL)
{ {
throw new InvalidDataException("Divisor must be > 1"); throw new InvalidDataException("Divisor must be > 1");
} }
if (divisor < (ulong)snapshot.Length) if (divisor < (ulong)snapshot.Length)
{ {
ulong cached = snapshot[divisor]; ulong cached = snapshot[divisor];
if (cached == 0UL) if (cached == 0UL)
{ {
throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
} }
// ObserveCycleCacheDivisor(divisor); // ObserveCycleCacheDivisor(divisor);
cycles[i] = cached; cycles[i] = cached;
} }
else else
{ {
missingBuffer[missingCount++] = i; missingBuffer[missingCount++] = i;
} }
} }
if (missingCount != 0) if (missingCount != 0)
{ {
ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount]; ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount];
if (_useGpuGeneration) if (_useGpuGeneration)
{ {
ComputeCyclesGpu(divisors, cycles, missingIndices); ComputeCyclesGpu(divisors, cycles, missingIndices);
} }
else else
{ {
ComputeCyclesCpu(divisors, cycles, missingIndices); ComputeCyclesCpu(divisors, cycles, missingIndices);
} }
} }
if (rentedMissing is not null) if (rentedMissing is not null)
{ {
ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false); ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false);
} }
} }
private static void ObserveCycleCacheDivisor(ulong divisor) private static void ObserveCycleCacheDivisor(ulong divisor)
{ {
bool logHit = false; bool logHit = false;
ulong hitNumber = 0UL; ulong hitNumber = 0UL;
bool trackingDisabledNow = false; bool trackingDisabledNow = false;
lock (CycleCacheTrackingLock) lock (CycleCacheTrackingLock)
{ {
if (CycleCacheTrackingDisabled) if (CycleCacheTrackingDisabled)
{ {
return; return;
} }
if (!CycleCacheTrackedDivisors.Add(divisor)) if (!CycleCacheTrackedDivisors.Add(divisor))
{ {
CycleCacheHitCount++; CycleCacheHitCount++;
hitNumber = CycleCacheHitCount; hitNumber = CycleCacheHitCount;
logHit = true; logHit = true;
} }
else if (CycleCacheTrackedDivisors.Count >= CycleCacheTrackingLimit) else if (CycleCacheTrackedDivisors.Count >= CycleCacheTrackingLimit)
{ {
CycleCacheTrackingDisabled = true; CycleCacheTrackingDisabled = true;
CycleCacheTrackedDivisors.Clear(); CycleCacheTrackedDivisors.Clear();
trackingDisabledNow = true; trackingDisabledNow = true;
} }
} }
if (logHit) if (logHit)
{ {
Console.WriteLine($"Cycle cache hit for divisor {divisor} ({hitNumber})"); Console.WriteLine($"Cycle cache hit for divisor {divisor} ({hitNumber})");
} }
else if (trackingDisabledNow) else if (trackingDisabledNow)
{ {
Console.WriteLine("Cycle cache hit tracking disabled after exceeding the tracking limit."); Console.WriteLine("Cycle cache hit tracking disabled after exceeding the tracking limit.");
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices) private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
{ {
for (int i = 0; i < indices.Length; i++) for (int i = 0; i < indices.Length; i++)
{ {
int targetIndex = indices[i]; int targetIndex = indices[i];
ulong divisor = divisors[targetIndex]; ulong divisor = divisors[targetIndex];
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData); cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData);
} }
} }
private void ComputeCyclesGpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices) private void ComputeCyclesGpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
{ {
int count = indices.Length; int count = indices.Length;
ulong[]? rentedDivisors = null; ulong[]? rentedDivisors = null;
Span<ulong> workDivisors = count <= StackBufferThreshold Span<ulong> workDivisors = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: new Span<ulong>(rentedDivisors = ThreadStaticPools.UlongPool.Rent(count), 0, count); : new Span<ulong>(rentedDivisors = ThreadStaticPools.UlongPool.Rent(count), 0, count);
ulong[]? rentedResults = null; ulong[]? rentedResults = null;
Span<ulong> workResults = count <= StackBufferThreshold Span<ulong> workResults = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: new Span<ulong>(rentedResults = ThreadStaticPools.UlongPool.Rent(count), 0, count); : new Span<ulong>(rentedResults = ThreadStaticPools.UlongPool.Rent(count), 0, count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
workDivisors[i] = divisors[indices[i]]; workDivisors[i] = divisors[indices[i]];
} }
ComputeCyclesGpuCore(workDivisors, workResults); ComputeCyclesGpuCore(workDivisors, workResults);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
cycles[indices[i]] = workResults[i]; cycles[indices[i]] = workResults[i];
} }
if (rentedDivisors is not null) if (rentedDivisors is not null)
{ {
ThreadStaticPools.UlongPool.Return(rentedDivisors, clearArray: false); ThreadStaticPools.UlongPool.Return(rentedDivisors, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedResults!, clearArray: false); ThreadStaticPools.UlongPool.Return(rentedResults!, clearArray: false);
} }
} }
private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination) private const int DefaultCapacity = 128;
{
int length = divisors.Length; [ThreadStatic]
var gpuLease = GpuKernelPool.GetKernel(); private static MemoryBuffer1D<ulong, Stride1D.Dense>? divisorBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? powBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? resultBuffer;
[ThreadStatic]
private static MemoryBuffer1D<byte, Stride1D.Dense>? statusBuffer;
private static void EnsureCapacity(int requiredCapacity)
{
if (requiredCapacity <= (divisorBuffer?.Length ?? 0))
{
return;
}
if (divisorBuffer != null)
{
divisorBuffer.Dispose();
powBuffer!.Dispose();
orderBuffer!.Dispose();
resultBuffer!.Dispose();
statusBuffer!.Dispose();
}
divisorBuffer = SharedGpuContext.Accelerator.Allocate1D<ulong>(requiredCapacity);
powBuffer = SharedGpuContext.Accelerator.Allocate1D<ulong>(requiredCapacity);
orderBuffer = SharedGpuContext.Accelerator.Allocate1D<ulong>(requiredCapacity);
resultBuffer = SharedGpuContext.Accelerator.Allocate1D<ulong>(requiredCapacity);
statusBuffer = SharedGpuContext.Accelerator.Allocate1D<byte>(requiredCapacity);
}
private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination)
{
int length = divisors.Length;
var gpuLease = GpuKernelPool.Rent();
Accelerator accelerator = gpuLease.Accelerator; Accelerator accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
var kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel); var kernel = _gpuKernel;
EnsureCapacity(length);
using MemoryBuffer1D<ulong, Stride1D.Dense> divisorBuffer = accelerator.Allocate1D<ulong>(length); ulong[]? rentedPow = null;
using MemoryBuffer1D<ulong, Stride1D.Dense> powBuffer = accelerator.Allocate1D<ulong>(length); Span<ulong> powSpan = length <= StackBufferThreshold
using MemoryBuffer1D<ulong, Stride1D.Dense> orderBuffer = accelerator.Allocate1D<ulong>(length); ? stackalloc ulong[length]
using MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = accelerator.Allocate1D<ulong>(length); : new Span<ulong>(rentedPow = ThreadStaticPools.UlongPool.Rent(length), 0, length);
using MemoryBuffer1D<byte, Stride1D.Dense> statusBuffer = accelerator.Allocate1D<byte>(length);
ulong[]? rentedPow = null; ulong[]? rentedOrder = null;
Span<ulong> powSpan = length <= StackBufferThreshold Span<ulong> orderSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: new Span<ulong>(rentedPow = ThreadStaticPools.UlongPool.Rent(length), 0, length); : new Span<ulong>(rentedOrder = ThreadStaticPools.UlongPool.Rent(length), 0, length);
ulong[]? rentedOrder = null; ulong[]? rentedResult = null;
Span<ulong> orderSpan = length <= StackBufferThreshold Span<ulong> resultSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: new Span<ulong>(rentedOrder = ThreadStaticPools.UlongPool.Rent(length), 0, length); : new Span<ulong>(rentedResult = ThreadStaticPools.UlongPool.Rent(length), 0, length);
ulong[]? rentedResult = null; byte[]? rentedStatus = null;
Span<ulong> resultSpan = length <= StackBufferThreshold Span<byte> statusSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc byte[length]
: new Span<ulong>(rentedResult = ThreadStaticPools.UlongPool.Rent(length), 0, length); : new Span<byte>(rentedStatus = ThreadStaticPools.BytePool.Rent(length), 0, length);
byte[]? rentedStatus = null; ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
Span<byte> statusSpan = length <= StackBufferThreshold divisorBuffer!.View.CopyFromCPU(stream, ref divisorRef, length);
? stackalloc byte[length]
: new Span<byte>(rentedStatus = ThreadStaticPools.BytePool.Rent(length), 0, length);
ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors); for (int i = 0; i < length; i++)
divisorBuffer.View.CopyFromCPU(stream, ref divisorRef, length); {
ulong divisor = divisors[i];
ulong initialPow = 2UL;
if (initialPow >= divisor)
{
initialPow -= divisor;
}
for (int i = 0; i < length; i++) powSpan[i] = initialPow;
{ orderSpan[i] = 1UL;
ulong divisor = divisors[i]; resultSpan[i] = 0UL;
ulong initialPow = 2UL; statusSpan[i] = ByteZero;
if (initialPow >= divisor) }
{
initialPow -= divisor;
}
powSpan[i] = initialPow; powBuffer!.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(powSpan), length);
orderSpan[i] = 1UL; orderBuffer!.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(orderSpan), length);
resultSpan[i] = 0UL; resultBuffer!.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(resultSpan), length);
statusSpan[i] = ByteZero; statusBuffer!.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(statusSpan), length);
}
powBuffer.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(powSpan), length); int pending;
orderBuffer.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(orderSpan), length); do
resultBuffer.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(resultSpan), length); {
statusBuffer.View.CopyFromCPU(stream, ref MemoryMarshal.GetReference(statusSpan), length);
int pending;
do
{
kernel(stream, length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View); kernel(stream, length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View);
statusBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(statusSpan), length); statusBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(statusSpan), length);
stream.Synchronize(); stream.Synchronize();
pending = 0; pending = 0;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
if (statusSpan[i] == ByteZero) if (statusSpan[i] == ByteZero)
{ {
pending++; pending++;
} }
} }
} }
while (pending > 0); while (pending > 0);
resultBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(resultSpan), length); resultBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(resultSpan), length);
stream.Synchronize(); stream.Synchronize();
resultSpan.CopyTo(destination);
if (rentedPow is not null)
{
ThreadStaticPools.UlongPool.Return(rentedPow, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedOrder!, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedResult!, clearArray: false);
ThreadStaticPools.BytePool.Return(rentedStatus!, clearArray: false);
}
gpuLease.Dispose(); resultSpan.CopyTo(destination);
} if (rentedPow is not null)
{
ThreadStaticPools.UlongPool.Return(rentedPow, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedOrder!, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedResult!, clearArray: false);
ThreadStaticPools.BytePool.Return(rentedStatus!, clearArray: false);
}
private static Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> LoadKernel(Accelerator accelerator) gpuLease.Dispose();
{ }
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(DivisorCycleKernels.GpuAdvanceDivisorCyclesKernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>>();
}
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator> private static Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> LoadKernel(Accelerator accelerator)
{ {
internal static AcceleratorReferenceComparer Instance { get; } = new(); var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(DivisorCycleKernels.GpuAdvanceDivisorCyclesKernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>>();
}
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y); private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
internal static AcceleratorReferenceComparer Instance { get; } = new();
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj); public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
}
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
}
} }
+7 -16
View File
@@ -83,7 +83,7 @@ public readonly struct ResiduePrimeViews(
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2; public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
} }
public readonly struct SmallPrimeFactorTables( public readonly struct SmallPrimeFactorViews(
MemoryBuffer1D<uint, Stride1D.Dense> primes, MemoryBuffer1D<uint, Stride1D.Dense> primes,
MemoryBuffer1D<ulong, Stride1D.Dense> squares, MemoryBuffer1D<ulong, Stride1D.Dense> squares,
int count) int count)
@@ -184,18 +184,13 @@ public class GpuKernelPool
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
} }
public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(KernelContainer kernels, Accelerator accelerator, AcceleratorStream stream) public static SmallPrimeFactorViews EnsureSmallPrimeFactorTables(KernelContainer kernels, Accelerator accelerator, AcceleratorStream stream)
{ {
if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer) if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
{ {
return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length); return new SmallPrimeFactorViews(primeBuffer, squareBuffer, (int)primeBuffer.Length);
} }
// if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
// {
// return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
// }
var hostPrimes = PrimesGenerator.SmallPrimes; var hostPrimes = PrimesGenerator.SmallPrimes;
var hostSquares = PrimesGenerator.SmallPrimesPow2; var hostSquares = PrimesGenerator.SmallPrimesPow2;
@@ -207,17 +202,13 @@ public class GpuKernelPool
kernels.SmallPrimeFactorsPrimes = devicePrimes; kernels.SmallPrimeFactorsPrimes = devicePrimes;
kernels.SmallPrimeFactorsSquares = deviceSquares; kernels.SmallPrimeFactorsSquares = deviceSquares;
return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); return new SmallPrimeFactorViews(devicePrimes, deviceSquares, hostPrimes.Length);
} }
public static GpuKernelLease Rent()
public static GpuKernelLease GetKernel()
{ {
GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpu = Rent(); var gpu = GpuContextPool.Rent();
var stream = gpu.Accelerator.CreateStream(); var stream = gpu.Accelerator.CreateStream();
var kernels = GetKernels(gpu.Accelerator, stream); var kernels = GetKernels(gpu.Accelerator, stream);
return GpuKernelLease.Rent(gpu, stream, kernels); return GpuKernelLease.Rent(gpu, stream, kernels);
@@ -229,7 +220,7 @@ public class GpuKernelPool
/// <param name="action">Action to run with (Accelerator, Stream).</param> /// <param name="action">Action to run with (Accelerator, Stream).</param>
public static void Run(Action<Accelerator, AcceleratorStream> action) public static void Run(Action<Accelerator, AcceleratorStream> action)
{ {
var lease = GetKernel(); var lease = Rent();
var accelerator = lease.Accelerator; var accelerator = lease.Accelerator;
var stream = lease.Stream; var stream = lease.Stream;
action(accelerator, stream); action(accelerator, stream);
@@ -95,7 +95,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong processedCount; ulong processedCount;
ulong lastProcessed; ulong lastProcessed;
var gpuLease = GpuKernelPool.GetKernel(); var gpuLease = GpuKernelPool.Rent();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
@@ -501,7 +501,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner)
{ {
_owner = owner; _owner = owner;
_lease = GpuKernelPool.GetKernel(); _lease = GpuKernelPool.Rent();
_accelerator = _lease.Accelerator; _accelerator = _lease.Accelerator;
_stream = _lease.Stream; _stream = _lease.Stream;
_capacity = Math.Max(1, owner._gpuBatchSize); _capacity = Math.Max(1, owner._gpuBatchSize);
@@ -16,7 +16,7 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
{ {
throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder})."); throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
var gpuLease = GpuKernelPool.GetKernel(); var gpuLease = GpuKernelPool.Rent();
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 int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
@@ -9,7 +9,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
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(); var gpuLease = GpuKernelPool.Rent();
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
@@ -198,7 +198,7 @@ internal static partial class PrimeOrderGpuHeuristics
try try
{ {
var lease = GpuKernelPool.GetKernel(); var lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
@@ -365,7 +365,7 @@ internal static partial class PrimeOrderGpuHeuristics
{ {
order = 0UL; order = 0UL;
var lease = GpuKernelPool.GetKernel(); var lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
@@ -526,7 +526,7 @@ internal static partial class PrimeOrderGpuHeuristics
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results) private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
{ {
var lease = GpuKernelPool.GetKernel(); var lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
var kernel = GetPow2ModKernel(accelerator); var kernel = GetPow2ModKernel(accelerator);
@@ -581,7 +581,7 @@ internal static partial class PrimeOrderGpuHeuristics
GpuUInt128[]? rentedExponents = null; GpuUInt128[]? rentedExponents = null;
GpuUInt128[]? rentedResults = null; GpuUInt128[]? rentedResults = null;
var lease = GpuKernelPool.GetKernel(); var lease = GpuKernelPool.Rent();
ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool; ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
try try
+1 -1
View File
@@ -804,7 +804,7 @@ public class MersenneDivisorCycles
divisors = pool.Rent(count); divisors = pool.Rent(count);
outCycles = pool.Rent(count); outCycles = pool.Rent(count);
var lease = GpuKernelPool.GetKernel(); var lease = GpuKernelPool.Rent();
var accelerator = lease.Accelerator; var accelerator = lease.Accelerator;
var stream = lease.Stream; var stream = lease.Stream;
var loaded = accelerator.LoadAutoGroupedStreamKernel< var loaded = accelerator.LoadAutoGroupedStreamKernel<
+1 -1
View File
@@ -147,7 +147,7 @@ public sealed class MersenneNumberTester(
UInt128[] qs = qsBuffer; UInt128[] qs = qsBuffer;
var gpuLease = GpuKernelPool.GetKernel(); var gpuLease = GpuKernelPool.Rent();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
var orderKernel = gpuLease.OrderKernel!; var orderKernel = gpuLease.OrderKernel!;
@@ -19,14 +19,14 @@ internal static partial class PrimeOrderCalculator
Span<int> exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); Span<int> exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
remaining = value; remaining = value;
var lease = GpuKernelPool.GetKernel(); var lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
var stream = lease.Stream; var stream = lease.Stream;
KernelContainer kernels = lease.Kernels; KernelContainer kernels = lease.Kernels;
ScratchBuffer scratch = GpuScratchBufferPool.Rent(accelerator, GpuSmallPrimeFactorSlots, 0); ScratchBuffer scratch = GpuScratchBufferPool.Rent(accelerator, GpuSmallPrimeFactorSlots, 0);
SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(kernels, accelerator, stream); SmallPrimeFactorViews tables = GpuKernelPool.EnsureSmallPrimeFactorTables(kernels, accelerator, stream);
if (kernels.SmallPrimeFactorsPrimes is null) throw new NullReferenceException($"{nameof(kernels.SmallPrimeFactorsPrimes)} is null"); if (kernels.SmallPrimeFactorsPrimes is null) throw new NullReferenceException($"{nameof(kernels.SmallPrimeFactorsPrimes)} is null");
if (kernels.SmallPrimeFactorsSquares is null) throw new NullReferenceException($"{nameof(kernels.SmallPrimeFactorsSquares)} is null"); if (kernels.SmallPrimeFactorsSquares is null) throw new NullReferenceException($"{nameof(kernels.SmallPrimeFactorsSquares)} is null");
@@ -100,7 +100,7 @@ internal static partial class PrimeOrderCalculator
factorSpan[i] = factors[i].Value; factorSpan[i] = factors[i].Value;
} }
GpuKernelLease lease = GpuKernelPool.GetKernel(); GpuKernelLease lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
var stream = lease.Stream; var stream = lease.Stream;
+21 -20
View File
@@ -440,7 +440,7 @@ public static partial class ULongExtensions
private static class Pow2MontgomeryGpuExecutor private static class Pow2MontgomeryGpuExecutor
{ {
private static readonly ConcurrentDictionary<Accelerator, Pow2MontgomeryKernelGroup> KernelCache = new(); private static readonly Pow2MontgomeryKernelGroup Kernel = Pow2MontgomeryKernelGroup.Create(SharedGpuContext.Accelerator);
public static ulong ExecuteKeep(ulong exponent, in MontgomeryDivisorData divisor) public static ulong ExecuteKeep(ulong exponent, in MontgomeryDivisorData divisor)
{ {
@@ -452,24 +452,25 @@ public static partial class ULongExtensions
return Execute(exponent, divisor, useKeepKernel: false); return Execute(exponent, divisor, useKeepKernel: false);
} }
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? _exponentBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? _resultBuffer;
private static ulong Execute(ulong exponent, in MontgomeryDivisorData divisor, bool useKeepKernel) private static ulong Execute(ulong exponent, in MontgomeryDivisorData divisor, bool useKeepKernel)
{ {
ulong result = 0UL; ulong result = 0UL;
GpuKernelLease lease = GpuKernelPool.GetKernel(); GpuKernelLease lease = GpuKernelPool.Rent();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
// Keep this commented out. It should never happen in production code. var kernelGroup = Kernel;
// if (accelerator.AcceleratorType == AcceleratorType.CPU)
// {
// return 0UL;
// }
var kernelGroup = KernelCache.GetOrAdd(accelerator, static accel => Pow2MontgomeryKernelGroup.Create(accel)); EnsureCapacity();
var exponentBuffer = accelerator.Allocate1D<ulong>(1); MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer = _exponentBuffer!;
var resultBuffer = accelerator.Allocate1D<ulong>(1); MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = _resultBuffer!;
exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1); exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1);
// We don't need to worry about any left-overs here. // We don't need to worry about any left-overs here.
@@ -483,19 +484,19 @@ public static partial class ULongExtensions
resultBuffer.Dispose(); resultBuffer.Dispose();
exponentBuffer.Dispose(); exponentBuffer.Dispose();
lease.Dispose(); lease.Dispose();
// Keep this commented. We don't want to catch any exceptions. All should crash the scanner.
// catch (AcceleratorException)
// {
// return 0UL;
// }
// catch (NotSupportedException)
// {
// return 0UL;
// }
// Intentionally avoid exception handling here; any accelerator failure should crash the scanner.
return result; return result;
} }
private static void EnsureCapacity()
{
if (_exponentBuffer == null)
{
Accelerator accelerator = SharedGpuContext.Accelerator;
_exponentBuffer = accelerator.Allocate1D<ulong>(1);
_resultBuffer = accelerator.Allocate1D<ulong>(1);
}
}
private sealed class Pow2MontgomeryKernelGroup private sealed class Pow2MontgomeryKernelGroup
{ {
internal Pow2MontgomeryKernelGroup( internal Pow2MontgomeryKernelGroup(