Various optimizations to separate non-static data between threads and resolve read/write access issue.

This commit is contained in:
mlemanczyk committed 2025-11-04 20:45:56 +01:00
1 parent 5a17fa64d5
commit 64d615c402
26 files changed
+1181 -1117

No files matched your search

+51 -136
View File
@@ -1,4 +1,3 @@
using PerfectNumbers.Core;
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
@@ -23,7 +22,8 @@ public readonly struct ResidueAutomatonArgs
}
}
public sealed class KernelContainer
public sealed class KernelContainer : IDisposable
{
// Serializes first-time initialization of kernels/buffers per accelerator.
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
@@ -38,42 +38,37 @@ public sealed class KernelContainer
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
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, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, Stride1D.Dense>>? SpecialMax;
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SpecialMax;
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorPrimeSlots;
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorExponentSlots;
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorCountSlot;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorRemainingSlot;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SpecialMaxFactors;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SpecialMaxCandidates;
public MemoryBuffer1D<ushort, Stride1D.Dense>? SpecialMaxResult;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
public void Dispose()
{
var current = Volatile.Read(ref slot);
if (current is not null)
{
return current;
}
// Order = null;
// Incremental = null;
// Pow2Mod = null;
// IncrementalOrder = null;
// Pow2ModOrder = null;
// SmallPrimeFactor = null;
// SpecialMax = null;
current = Volatile.Read(ref slot);
if (current is null)
{
current = factory();
Volatile.Write(ref slot, current);
}
return current;
// SmallCycles?.Dispose();
// SmallPrimesLastOne?.Dispose();
// SmallPrimesLastSeven?.Dispose();
// SmallPrimesPow2LastOne?.Dispose();
// SmallPrimesPow2LastSeven?.Dispose();
// SmallPrimeFactorsPrimes?.Dispose();
// SmallPrimeFactorsSquares?.Dispose();
}
public void Dispose() => GpuKernelPool.Return(this);
}
public readonly struct ResiduePrimeViews(
@@ -101,49 +96,30 @@ public readonly struct SmallPrimeFactorTables(
public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View;
}
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 = primeSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots = exponentSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot = countSlot;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot = remainingSlot;
public void Clear()
{
PrimeSlots.MemSetToZero();
ExponentSlots.MemSetToZero();
CountSlot.MemSetToZero();
RemainingSlot.MemSetToZero();
}
}
public readonly struct SpecialMaxScratch(
MemoryBuffer1D<ulong, Stride1D.Dense> factorValues,
MemoryBuffer1D<ulong, Stride1D.Dense> candidateValues,
MemoryBuffer1D<ushort, Stride1D.Dense> resultSlot)
{
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> FactorValues = factorValues;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> CandidateValues = candidateValues;
public readonly MemoryBuffer1D<ushort, Stride1D.Dense> ResultSlot = resultSlot;
public ArrayView1D<ulong, Stride1D.Dense> FactorsView => FactorValues.View;
public ArrayView1D<ulong, Stride1D.Dense> CandidatesView => CandidateValues.View;
public ArrayView1D<ushort, Stride1D.Dense> ResultView => ResultSlot.View;
}
public class GpuKernelPool
{
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 readonly ConcurrentDictionary<Accelerator, KernelContainer> _kernels = new();
public static KernelContainer GetKernels() => KernelCache.TryDequeue(out var container)
? container
: new KernelContainer();
public static T InitOnce<T>(ref T? target, Func<T> valueFactory) where T : class
{
if (target is { } value)
{
return value;
}
public static void Return(KernelContainer container) => KernelCache.Enqueue(container);
var newValue = valueFactory();
return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue;
}
internal static KernelContainer GetKernels(Accelerator accelerator)
{
return _kernels.GetOrAdd(accelerator, static (accelerator) =>
{
var kernels = new KernelContainer();
PreloadStaticTables(kernels, accelerator);
return kernels;
});
}
internal static void PreloadStaticTables(KernelContainer kernels, Accelerator accelerator)
{
@@ -215,10 +191,10 @@ public class GpuKernelPool
return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length);
}
if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
{
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
}
// if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
// {
// return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
// }
var hostPrimes = PrimesGenerator.SmallPrimes;
var hostSquares = PrimesGenerator.SmallPrimesPow2;
@@ -234,74 +210,15 @@ public class GpuKernelPool
return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length);
}
public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(KernelContainer kernels, Accelerator accelerator, int slotCount)
{
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;
if (exponentSlots is null || exponentSlots.Length < slotCount)
{
exponentSlots?.Dispose();
exponentSlots = accelerator.Allocate1D<int>(slotCount);
kernels.SmallPrimeFactorExponentSlots = exponentSlots;
}
MemoryBuffer1D<int, Stride1D.Dense>? countSlot = kernels.SmallPrimeFactorCountSlot;
if (countSlot is null)
{
countSlot = accelerator.Allocate1D<int>(1);
kernels.SmallPrimeFactorCountSlot = countSlot;
}
MemoryBuffer1D<ulong, Stride1D.Dense>? remainingSlot = kernels.SmallPrimeFactorRemainingSlot;
if (remainingSlot is null)
{
remainingSlot = accelerator.Allocate1D<ulong>(1);
kernels.SmallPrimeFactorRemainingSlot = remainingSlot;
}
return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!);
}
public static SpecialMaxScratch EnsureSpecialMaxScratch(KernelContainer kernels, Accelerator accelerator, int factorCapacity)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? factorValues = kernels.SpecialMaxFactors;
if (factorValues is null || factorValues.Length < factorCapacity)
{
factorValues?.Dispose();
factorValues = accelerator.Allocate1D<ulong>(factorCapacity);
kernels.SpecialMaxFactors = factorValues;
}
MemoryBuffer1D<ulong, Stride1D.Dense>? candidateValues = kernels.SpecialMaxCandidates;
if (candidateValues is null || candidateValues.Length < factorCapacity)
{
candidateValues?.Dispose();
candidateValues = accelerator.Allocate1D<ulong>(factorCapacity);
kernels.SpecialMaxCandidates = candidateValues;
}
MemoryBuffer1D<ushort, Stride1D.Dense>? resultSlot = kernels.SpecialMaxResult;
if (resultSlot is null)
{
resultSlot = accelerator.Allocate1D<ushort>(1);
kernels.SpecialMaxResult = resultSlot;
}
return new SpecialMaxScratch(factorValues!, candidateValues!, resultSlot!);
}
public static GpuKernelLease GetKernel(bool useGpuOrder)
public static GpuKernelLease GetKernel()
{
GpuPrimeWorkLimiter.Acquire();
var gpu = Rent();
var kernels = GetKernels();
var kernels = GetKernels(gpu.Accelerator);
return GpuKernelLease.Rent(gpu, kernels);
}
@@ -311,12 +228,10 @@ public class GpuKernelPool
/// <param name="action">Action to run with (Accelerator, Stream).</param>
public static void Run(Action<Accelerator, AcceleratorStream> action)
{
var lease = GetKernel(useGpuOrder: true);
var lease = GetKernel();
var accelerator = lease.Accelerator;
var stream = accelerator.CreateStream();
var stream = lease.Stream;
action(accelerator, stream);
stream.Dispose();
lease.Dispose();
}
}