Various optimizations

This commit is contained in:
mlemanczyk committed 2025-10-29 14:00:38 +01:00
1 parent 3147728d5b
commit 4c79c4087e
9 files changed
+51 -101

No files matched your search

+1 -1
View File
@@ -55,7 +55,7 @@ public class ProgramTests
[InlineData(81UL, false)] [InlineData(81UL, false)]
public void IsPrime_identifies_primes_correctly(ulong n, bool expected) public void IsPrime_identifies_primes_correctly(ulong n, bool expected)
{ {
new PrimeTester().IsPrime(n, CancellationToken.None).Should().Be(expected); PrimeTester.IsPrime(n, CancellationToken.None).Should().Be(expected);
} }
[Fact] [Fact]
+1 -1
View File
@@ -622,7 +622,7 @@ internal static class Program
if (!_cliArguments.UseByDivisor) if (!_cliArguments.UseByDivisor)
{ {
if (!(GpuContextPool.ForceCpu if (!(GpuContextPool.ForceCpu
? PrimeTesters.Value!.IsPrime(p, CancellationToken.None) ? PrimeTester.IsPrime(p, CancellationToken.None)
: PrimeTesters.Value!.IsPrimeGpu(p, CancellationToken.None))) : PrimeTesters.Value!.IsPrimeGpu(p, CancellationToken.None)))
{ {
_lastCompositeP = true; _lastCompositeP = true;
@@ -26,7 +26,7 @@ public class PrimeTesterIsPrimeTests
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
tester.IsPrime(n, CancellationToken.None).Should().Be(expected); PrimeTester.IsPrime(n, CancellationToken.None).Should().Be(expected);
} }
[Theory] [Theory]
+2 -11
View File
@@ -3,7 +3,6 @@ namespace PerfectNumbers.Core.Gpu;
public static class GpuPrimeWorkLimiter public static class GpuPrimeWorkLimiter
{ {
private static SemaphoreSlim _semaphore = new(1, int.MaxValue); private static SemaphoreSlim _semaphore = new(1, int.MaxValue);
private static readonly object _lock = new();
private static int _currentLimit = 1; private static int _currentLimit = 1;
public static Lease Acquire() public static Lease Acquire()
@@ -11,18 +10,11 @@ public static class GpuPrimeWorkLimiter
// TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve // TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
// GPU jobs stop allocating Lease instances on every acquisition. // GPU jobs stop allocating Lease instances on every acquisition.
var sem = _semaphore; var sem = _semaphore;
sem.Wait(); // sem.Wait();
return new Lease(sem); return new Lease(sem);
} }
public static void SetLimit(int value) public static void SetLimit(int value)
{
if (value < 1)
{
value = 1;
}
lock (_lock)
{ {
if (value == _currentLimit) if (value == _currentLimit)
{ {
@@ -34,7 +26,6 @@ public static class GpuPrimeWorkLimiter
_semaphore = new SemaphoreSlim(value, value); _semaphore = new SemaphoreSlim(value, value);
_currentLimit = value; _currentLimit = value;
} }
}
public sealed class Lease public sealed class Lease
{ {
@@ -47,7 +38,7 @@ public static class GpuPrimeWorkLimiter
public void Dispose() public void Dispose()
{ {
_sem.Release(); // _sem.Release();
} }
} }
} }
@@ -94,7 +94,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
var gpuLease = RentAccelerator(); var gpuLease = RentAccelerator();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
Monitor.Enter(gpuLease.ExecutionLock); // Monitor.Enter(gpuLease.ExecutionLock);
var kernel = GetKernel(accelerator); var kernel = GetKernel(accelerator);
var resources = RentBatchResources(accelerator, batchCapacity); var resources = RentBatchResources(accelerator, batchCapacity);
@@ -121,7 +121,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
out processedCount); out processedCount);
ReturnBatchResources(resources); ReturnBatchResources(resources);
Monitor.Exit(gpuLease.ExecutionLock); // Monitor.Exit(gpuLease.ExecutionLock);
ReturnAccelerator(gpuLease); ReturnAccelerator(gpuLease);
if (composite) if (composite)
@@ -560,7 +560,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
} }
Monitor.Enter(_lease.ExecutionLock); // Monitor.Enter(_lease.ExecutionLock);
EnsureExecutionResourcesLocked(length); EnsureExecutionResourcesLocked(length);
@@ -601,7 +601,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice); ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice);
hitView.CopyToCPU(ref hitRef, length); hitView.CopyToCPU(ref hitRef, length);
Monitor.Exit(_lease.ExecutionLock); // Monitor.Exit(_lease.ExecutionLock);
} }
public void Dispose() public void Dispose()
@@ -30,7 +30,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
var gpuLease = RentAccelerator(); var gpuLease = RentAccelerator();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
Monitor.Enter(gpuLease.ExecutionLock); // Monitor.Enter(gpuLease.ExecutionLock);
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetPow2ModKernel(accelerator); var kernel = GetPow2ModKernel(accelerator);
@@ -108,7 +108,7 @@ 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); ReturnAccelerator(gpuLease);
limiter.Dispose(); limiter.Dispose();
} }
@@ -112,19 +112,16 @@ public sealed class HeuristicCombinedPrimeTester
} }
public static int HeuristicGpuDivisorBatchSize { get; set; } = 4_096; internal static int HeuristicGpuDivisorBatchSize = 4_096;
internal static int HeuristicDivisorInterleaveBatchSize = 64;
public static int HeuristicDivisorInterleaveBatchSize { get; set; } = 64;
[ThreadStatic] [ThreadStatic]
private static HeuristicCombinedPrimeTester? _tester; private static HeuristicCombinedPrimeTester? _tester;
public static HeuristicCombinedPrimeTester Exclusive => _tester ??= new(); public static HeuristicCombinedPrimeTester Exclusive => _tester ??= new();
public bool IsPrimeCpu(ulong n, CancellationToken ct) public static bool IsPrimeCpu(ulong n)
{ {
ct.ThrowIfCancellationRequested();
byte nMod10 = (byte)n.Mod10(); byte nMod10 = (byte)n.Mod10();
ulong sqrtLimit = ComputeHeuristicSqrt(n); ulong sqrtLimit = ComputeHeuristicSqrt(n);
@@ -136,24 +133,14 @@ public sealed class HeuristicCombinedPrimeTester
return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10); return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10);
} }
public bool IsPrimeCpu(ulong n) public static bool IsPrimeGpu(ulong n)
{
return IsPrimeCpu(n, CancellationToken.None);
}
public bool IsPrimeGpu(ulong n)
{ {
byte nMod10 = (byte)n.Mod10(); byte nMod10 = (byte)n.Mod10();
ulong sqrtLimit = ComputeHeuristicSqrt(n); ulong sqrtLimit = ComputeHeuristicSqrt(n);
return IsPrimeGpu(n, sqrtLimit, nMod10); return IsPrimeGpu(n, sqrtLimit, nMod10);
} }
public bool IsPrimeGpu(ulong n, ulong sqrtLimit, byte nMod10) public static bool IsPrimeGpu(ulong n, ulong sqrtLimit, byte nMod10)
{
return HeuristicIsPrimeGpuCore(n, sqrtLimit, nMod10);
}
private bool HeuristicIsPrimeGpuCore(ulong n, ulong sqrtLimit, byte nMod10)
{ {
// TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner? // TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner?
if (sqrtLimit < 3UL) if (sqrtLimit < 3UL)
@@ -161,11 +148,6 @@ public sealed class HeuristicCombinedPrimeTester
return EvaluateWithOpenNumericFallback(n); return EvaluateWithOpenNumericFallback(n);
} }
if (!UseHeuristicGroupBTrialDivision)
{
return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10);
}
bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, sqrtLimit, nMod10); bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, sqrtLimit, nMod10);
return !compositeDetected; return !compositeDetected;
} }
@@ -196,7 +178,7 @@ public sealed class HeuristicCombinedPrimeTester
return EvaluateWithOpenNumericFallback(n); return EvaluateWithOpenNumericFallback(n);
} }
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10) private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10)
{ {
int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize); int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize);
var divisorPool = ThreadStaticPools.UlongPool; var divisorPool = ThreadStaticPools.UlongPool;
@@ -214,8 +196,6 @@ public sealed class HeuristicCombinedPrimeTester
bool compositeDetected = false; bool compositeDetected = false;
int count = 0; int count = 0;
lock (gpu.ExecutionLock)
{
var input = scratch.Input; var input = scratch.Input;
var output = scratch.Output; var output = scratch.Output;
@@ -238,11 +218,12 @@ public sealed class HeuristicCombinedPrimeTester
continue; continue;
} }
ulong divisor = divisorArray[i];
if (divisor > 1UL && n % divisor == 0UL)
{
return true; return true;
} // ulong divisor = divisorArray[i];
// if (divisor > 1UL && n % divisor == 0UL)
// {
// return true;
// }
} }
return false; return false;
@@ -285,9 +266,6 @@ public sealed class HeuristicCombinedPrimeTester
compositeDetected = true; compositeDetected = true;
goto Cleanup; goto Cleanup;
} }
count = 0;
}
} }
Cleanup: Cleanup:
+7 -7
View File
@@ -122,10 +122,10 @@ public sealed class HeuristicPrimeTester
return EvaluateWithOpenNumericFallback(n); return EvaluateWithOpenNumericFallback(n);
} }
if (!UseHeuristicGroupBTrialDivision) // if (!UseHeuristicGroupBTrialDivision)
{ // {
return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10, includeGroupB: false); // return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10, includeGroupB: false);
} // }
bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, sqrtLimit, nMod10); bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, sqrtLimit, nMod10);
return !compositeDetected; return !compositeDetected;
@@ -260,8 +260,8 @@ public sealed class HeuristicPrimeTester
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10) private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10)
{ {
int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize); int batchCapacity = HeuristicGpuDivisorBatchSize;
int interleaveBatchSize = Math.Max(1, HeuristicDivisorInterleaveBatchSize); int interleaveBatchSize = HeuristicDivisorInterleaveBatchSize;
var divisorPool = ThreadStaticPools.UlongPool; var divisorPool = ThreadStaticPools.UlongPool;
var hitPool = ThreadStaticPools.BytePool; var hitPool = ThreadStaticPools.BytePool;
@@ -277,7 +277,7 @@ public sealed class HeuristicPrimeTester
bool compositeDetected = false; bool compositeDetected = false;
int count = 0; int count = 0;
lock (gpu.ExecutionLock) // lock (gpu.ExecutionLock)
{ {
var input = scratch.Input; var input = scratch.Input;
var output = scratch.Output; var output = scratch.Output;
+22 -41
View File
@@ -1,11 +1,8 @@
using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -22,7 +19,7 @@ public sealed class PrimeTester
public static PrimeTester Exclusive => _tester ??= new(); public static PrimeTester Exclusive => _tester ??= new();
public bool IsPrime(ulong n, CancellationToken ct) public static bool IsPrime(ulong n, CancellationToken ct)
{ {
if (n <= 1UL) if (n <= 1UL)
{ {
@@ -121,7 +118,7 @@ public sealed class PrimeTester
public static bool IsPrimeCpu(ulong n, CancellationToken ct) public static bool IsPrimeCpu(ulong n, CancellationToken ct)
{ {
// Preserve the legacy entry point for callers that bypass the thread-local caches. // Preserve the legacy entry point for callers that bypass the thread-local caches.
return Exclusive.IsPrime(n, ct); return IsPrime(n, ct);
} }
@@ -137,8 +134,6 @@ public sealed class PrimeTester
int totalLength = values.Length; int totalLength = values.Length;
int batchSize = Math.Max(1, GpuBatchSize); int batchSize = Math.Max(1, GpuBatchSize);
lock (gpu.ExecutionLock)
{
var scratch = state.RentScratch(batchSize, accelerator); var scratch = state.RentScratch(batchSize, accelerator);
var input = scratch.Input; var input = scratch.Input;
var output = scratch.Output; var output = scratch.Output;
@@ -176,7 +171,6 @@ public sealed class PrimeTester
pool.Return(temp, clearArray: false); pool.Return(temp, clearArray: false);
state.ReturnScratch(scratch); state.ReturnScratch(scratch);
}
gpu.Dispose(); gpu.Dispose();
limiter.Dispose(); limiter.Dispose();
@@ -190,8 +184,6 @@ public sealed class PrimeTester
public Accelerator Accelerator { get; } public Accelerator Accelerator { get; }
public object ExecutionLock { get; } = new();
private KernelState? _kernelState; private KernelState? _kernelState;
public PooledContext() public PooledContext()
@@ -226,7 +218,6 @@ public sealed class PrimeTester
} }
private static readonly ConcurrentQueue<PooledContext> Pool = new(); private static readonly ConcurrentQueue<PooledContext> Pool = new();
private static readonly object CreationLock = new();
internal static PrimeTesterGpuContextLease Rent() internal static PrimeTesterGpuContextLease Rent()
{ {
@@ -235,11 +226,8 @@ public sealed class PrimeTester
return new PrimeTesterGpuContextLease(ctx); return new PrimeTesterGpuContextLease(ctx);
} }
lock (CreationLock)
{
return new PrimeTesterGpuContextLease(new PooledContext()); return new PrimeTesterGpuContextLease(new PooledContext());
} }
}
internal static void DisposeAll() internal static void DisposeAll()
{ {
@@ -255,22 +243,16 @@ public sealed class PrimeTester
Pool.Enqueue(ctx); Pool.Enqueue(ctx);
} }
internal struct PrimeTesterGpuContextLease internal readonly struct PrimeTesterGpuContextLease(PrimeTesterGpuContextPool.PooledContext ctx)
{ {
private PooledContext? _ctx; private readonly PooledContext? _ctx = ctx;
internal PrimeTesterGpuContextLease(PooledContext ctx) public readonly Accelerator Accelerator => _ctx!.Accelerator;
{
_ctx = ctx;
}
public Accelerator Accelerator => _ctx!.Accelerator;
public object ExecutionLock => _ctx!.ExecutionLock; public readonly KernelState State => _ctx!.KernelState;
public KernelState State => _ctx!.KernelState; public readonly void Dispose()
public void Dispose()
{ {
Return(_ctx ?? throw new InvalidOperationException("GPU context lease is not initialized.")); Return(_ctx ?? throw new InvalidOperationException("GPU context lease is not initialized."));
} }
@@ -280,20 +262,20 @@ public sealed class PrimeTester
// Per-accelerator GPU state for prime sieve (kernel + uploaded primes). // Per-accelerator GPU state for prime sieve (kernel + uploaded primes).
internal sealed class KernelState internal sealed class KernelState
{ {
public 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 { get; } public 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 Action<Index1D, ArrayView<ulong>, ulong, ArrayView<byte>> HeuristicTrialDivisionKernel { get; } public Action<Index1D, ArrayView<ulong>, ulong, ArrayView<byte>> HeuristicTrialDivisionKernel;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault { get; } public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne { get; } public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven { get; } public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastThree { get; } public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastThree;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastNine { get; } public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastNine;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2Default { get; } public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2Default;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne { get; } public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven { get; } public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree { get; } public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine { get; } public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine;
public bool IsDisposed { get; private set; } public bool IsDisposed;
private readonly System.Collections.Concurrent.ConcurrentBag<ScratchBuffers> _scratchPool = []; private readonly ConcurrentBag<ScratchBuffers> _scratchPool = [];
// TODO: Replace this ConcurrentBag with the lock-free ring buffer variant validated in // TODO: Replace this ConcurrentBag with the lock-free ring buffer variant validated in
// GpuModularArithmeticBenchmarks so renting scratch buffers stops contending on the bag's internal locks when // GpuModularArithmeticBenchmarks so renting scratch buffers stops contending on the bag's internal locks when
// thousands of GPU batches execute per second. // thousands of GPU batches execute per second.
@@ -369,7 +351,6 @@ public sealed class PrimeTester
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ScratchBuffers RentScratch(int minCapacity, Accelerator accel) public ScratchBuffers RentScratch(int minCapacity, Accelerator accel)
{ {
minCapacity = Math.Max(1, minCapacity);
while (_scratchPool.TryTake(out var sb)) while (_scratchPool.TryTake(out var sb))
{ {
if (sb.Capacity >= minCapacity) if (sb.Capacity >= minCapacity)
@@ -428,7 +409,7 @@ public sealed class PrimeTester
{ {
var lazy = States.GetOrAdd( var lazy = States.GetOrAdd(
accelerator, accelerator,
acc => new Lazy<KernelState>(() => new KernelState(acc), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication)); acc => new Lazy<KernelState>(() => new KernelState(acc), LazyThreadSafetyMode.ExecutionAndPublication));
return lazy.Value; return lazy.Value;
} }