Initial commit.
This commit is contained in:
1 parent
e5b0fea18c
commit
9f35f6c558
94 files changed
+12857
No files matched your search
@@ -0,0 +1,7 @@
|
||||
namespace PerfectNumbers.Core.Gpu
|
||||
{
|
||||
public static class GpuConstants
|
||||
{
|
||||
public const int ScanBatchSize = 2_097_152;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public static class GpuContextPool
|
||||
{
|
||||
private static readonly bool PoolingEnabled = false;
|
||||
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
|
||||
public static bool ForceCpu { get; set; } = false;
|
||||
|
||||
internal sealed class PooledContext
|
||||
{
|
||||
private bool disposed;
|
||||
|
||||
public Context Context { get; }
|
||||
public Accelerator Accelerator { get; }
|
||||
public bool IsCpu { get; }
|
||||
|
||||
public PooledContext(bool preferCpu)
|
||||
{
|
||||
Context = Context.CreateDefault();
|
||||
Accelerator = Context.GetPreferredDevice(preferCpu).CreateAccelerator(Context);
|
||||
IsCpu = Accelerator.AcceleratorType == AcceleratorType.CPU;
|
||||
// NOTE: Avoid loading/compiling any kernel here to prevent implicit
|
||||
// CL stream/queue creation during accelerator construction.
|
||||
// Some OpenCL drivers are fragile when a queue is created immediately
|
||||
// after device init. Real kernels will be JIT-loaded at first use.
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure all cached GPU buffers for this accelerator are released.
|
||||
NttGpuMath.ClearCaches(Accelerator);
|
||||
// Release PrimeTester GPU state for this accelerator (kernel/device primes)
|
||||
// Clear any per-accelerator cached resources to avoid releasing
|
||||
// after the accelerator is destroyed.
|
||||
PrimeTester.ClearGpuCaches(Accelerator);
|
||||
|
||||
Accelerator.Dispose();
|
||||
Context.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentQueue<PooledContext> CpuPool = new();
|
||||
private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
|
||||
private static readonly object CreationLock = new();
|
||||
|
||||
public static GpuContextLease Rent()
|
||||
{
|
||||
return RentPreferred(ForceCpu);
|
||||
}
|
||||
|
||||
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
|
||||
public static GpuContextLease RentPreferred(bool preferCpu)
|
||||
{
|
||||
if (PoolingEnabled)
|
||||
{
|
||||
if (preferCpu && CpuPool.TryDequeue(out var cpu))
|
||||
{
|
||||
return new GpuContextLease(cpu, CreationLock);
|
||||
}
|
||||
|
||||
if (!preferCpu && GpuPool.TryDequeue(out var gpu))
|
||||
{
|
||||
return new GpuContextLease(gpu, CreationLock);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize accelerator creation to avoid concurrent CL device/stream setup across threads.
|
||||
lock (CreationLock)
|
||||
{
|
||||
return new GpuContextLease(new PooledContext(preferCpu), CreationLock);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisposeAll()
|
||||
{
|
||||
if (!PoolingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (CpuPool.TryDequeue(out var cpu))
|
||||
{
|
||||
cpu.Dispose();
|
||||
}
|
||||
|
||||
while (GpuPool.TryDequeue(out var gpu))
|
||||
{
|
||||
gpu.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Return(PooledContext ctx)
|
||||
{
|
||||
if (PoolingEnabled)
|
||||
{
|
||||
ctx.Accelerator.Synchronize();
|
||||
if (ctx.IsCpu)
|
||||
{
|
||||
CpuPool.Enqueue(ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
GpuPool.Enqueue(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
ctx.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public struct GpuContextLease : IDisposable
|
||||
{
|
||||
private readonly PooledContext _ctx;
|
||||
private bool disposed;
|
||||
|
||||
internal GpuContextLease(PooledContext ctx, object kernelInitLock)
|
||||
{
|
||||
_ctx = ctx;
|
||||
KernelInitLock = kernelInitLock;
|
||||
}
|
||||
|
||||
public Context Context => _ctx.Context;
|
||||
|
||||
public Accelerator Accelerator => _ctx.Accelerator;
|
||||
|
||||
public object KernelInitLock { get; }
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Return(_ctx);
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,550 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using static PerfectNumbers.Core.Gpu.GpuContextPool;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public readonly struct ResidueAutomatonArgs
|
||||
{
|
||||
public readonly ulong Q0M10;
|
||||
public readonly ulong Step10;
|
||||
public readonly ulong Q0M8;
|
||||
public readonly ulong Step8;
|
||||
public readonly ulong Q0M3;
|
||||
public readonly ulong Step3;
|
||||
public readonly ulong Q0M5;
|
||||
public readonly 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;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class KernelContainer
|
||||
{
|
||||
// Serializes first-time initialization of kernels/buffers per accelerator.
|
||||
public Action<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
|
||||
public Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>? Incremental;
|
||||
public Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>? Pow2Mod;
|
||||
public Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>? IncrementalOrder;
|
||||
public Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>? Pow2ModOrder;
|
||||
|
||||
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallCycles;
|
||||
|
||||
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
|
||||
{
|
||||
var current = Volatile.Read(ref slot);
|
||||
if (current is not null)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
current = Volatile.Read(ref slot);
|
||||
if (current is null)
|
||||
{
|
||||
current = factory();
|
||||
Volatile.Write(ref slot, current);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels)
|
||||
{
|
||||
private bool disposedValue;
|
||||
private IDisposable? _limiter = limiter;
|
||||
private GpuContextLease? _gpu = gpu;
|
||||
private KernelContainer _kernels = kernels;
|
||||
|
||||
public readonly Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released");
|
||||
|
||||
public readonly Action<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator; // avoid capturing 'this' in lambda
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Order, () => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernelScan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>> Pow2ModKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>(Pow2ModKernelScan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>> IncrementalKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Incremental, () => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>(IncrementalKernelScan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>> IncrementalOrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>(IncrementalOrderKernelScan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>> Pow2ModOrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>(Pow2ModOrderKernelScan));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
||||
// (some already accept it). Consider a compact type (byte/ushort) for memory footprint.
|
||||
|
||||
private static void IncrementalKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong divMul,
|
||||
ulong q0m10, ulong q0m8, ulong q0m3, ulong q0m5, ArrayView<ulong> orders,
|
||||
ArrayView1D<uint, Stride1D.Dense> smallCycles)
|
||||
{
|
||||
ulong idx = (ulong)index.X;
|
||||
// residue automaton
|
||||
ulong step10 = ((exponent % 10UL) << 1) % 10UL;
|
||||
ulong r10 = q0m10 + (step10 * idx) % 10UL;
|
||||
r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL; // skip q ≡ 5 (mod 10); other residues handled by r8/r3/r5
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong r8 = (q0m8 + ((step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong r3 = q0m3 + (step3 * (idx % 3UL)); r3 -= (r3 >= 3UL) ? 3UL : 0UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong step5Loc = ((exponent % 5UL) << 1) % 5UL;
|
||||
ulong r5 = q0m5 + (step5Loc * (idx % 5UL)); if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldCheck)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One;
|
||||
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1))
|
||||
{
|
||||
uint cyc = smallCycles[(int)q.Low];
|
||||
if (cyc != 0U && (exponent % cyc) == 0UL)
|
||||
{
|
||||
orders[index] = exponent;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GpuUInt128 phi = q - GpuUInt128.One;
|
||||
if (phi.High != 0UL)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
ulong phi64 = phi.Low;
|
||||
if (GpuUInt128.Pow2Mod(phi64, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
ulong div = phi64.FastDiv64(exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
orders[index] = 1UL;
|
||||
}
|
||||
|
||||
private static void OrderKernelScan(Index1D index, ulong exponent, ulong divMul, ArrayView<GpuUInt128> qs, ArrayView<ulong> orders)
|
||||
{
|
||||
GpuUInt128 q = qs[index];
|
||||
GpuUInt128 phi = q - GpuUInt128.One;
|
||||
if (phi.High != 0UL)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
ulong phi64 = phi.Low;
|
||||
GpuUInt128 pow = GpuUInt128.Pow2Mod(phi64, q);
|
||||
if (pow != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
ulong div = phi64.FastDiv64(exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
orders[index] = exponent;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_gpu?.Dispose();
|
||||
_gpu = null;
|
||||
|
||||
_limiter?.Dispose();
|
||||
_limiter = null;
|
||||
}
|
||||
|
||||
// free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Pow2ModKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _,
|
||||
ulong q0m10, ulong q0m8, ulong q0m3, ulong q0m5, ArrayView<ulong> orders,
|
||||
ArrayView1D<uint, Stride1D.Dense> smallCycles)
|
||||
{
|
||||
ulong idx = (ulong)index.X;
|
||||
ulong step10 = ((exponent % 10UL) << 1) % 10UL;
|
||||
ulong r10 = q0m10 + (step10 * idx) % 10UL; r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong r8 = (q0m8 + ((step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong r3 = q0m3 + (step3 * (idx % 3UL)); r3 -= (r3 >= 3UL) ? 3UL : 0UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong step5Loc = ((exponent % 5UL) << 1) % 5UL;
|
||||
ulong r5 = q0m5 + (step5Loc * (idx % 5UL)); if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One;
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1))
|
||||
{
|
||||
uint cyc = smallCycles[(int)q.Low];
|
||||
if (cyc != 0U && (exponent % cyc) == 0UL)
|
||||
{
|
||||
orders[index] = exponent;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (GpuUInt128.Pow2Mod(exponent, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
orders[index] = exponent;
|
||||
}
|
||||
|
||||
// Device-friendly small cycle length for 2 mod divisor
|
||||
private static ulong CalculateCycleLengthSmall(ulong divisor)
|
||||
{
|
||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
return 1UL;
|
||||
|
||||
ulong order = 1UL, pow = 2UL;
|
||||
while (pow != 1UL)
|
||||
{
|
||||
pow <<= 1;
|
||||
if (pow > divisor)
|
||||
pow -= divisor;
|
||||
|
||||
order++;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
private static void IncrementalOrderKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong divMul,
|
||||
ResidueAutomatonArgs ra, ArrayView<int> found, ArrayView1D<uint, Stride1D.Dense> smallCycles)
|
||||
{
|
||||
ulong idx = (ulong)index.X;
|
||||
ulong r10 = ra.Q0M10 + (ra.Step10 * idx) % 10UL; r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * (idx % 3UL)); r3 -= (r3 >= 3UL) ? 3UL : 0UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * (idx % 5UL)); if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One;
|
||||
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1))
|
||||
{
|
||||
uint cyc = smallCycles[(int)q.Low];
|
||||
if (cyc != 0U && (exponent % (ulong)cyc) == 0UL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
GpuUInt128 phi = q - GpuUInt128.One;
|
||||
if (phi.High != 0UL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong phi64 = phi.Low;
|
||||
if (GpuUInt128.Pow2Mod(phi64, q) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong div = phi64.FastDiv64(exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Atomic.Or(ref found[0], 1);
|
||||
}
|
||||
|
||||
private static void Pow2ModOrderKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _,
|
||||
ResidueAutomatonArgs ra, ArrayView<int> found, ArrayView1D<uint, Stride1D.Dense> smallCycles)
|
||||
{
|
||||
ulong idx = (ulong)index.X;
|
||||
ulong r10 = ra.Q0M10 + (ra.Step10 * idx) % 10UL; r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * (idx % 3UL)); r3 -= (r3 >= 3UL) ? 3UL : 0UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * (idx % 5UL)); if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One;
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low <= (ulong)(smallCycles.Length - 1))
|
||||
{
|
||||
uint cyc = smallCycles[(int)q.Low];
|
||||
if (cyc != 0U && (exponent % (ulong)cyc) == 0UL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (GpuUInt128.Pow2Mod(exponent, q) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Atomic.Or(ref found[0], 1);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
// Enabled this when the struct is converted to a class
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class GpuKernelPool
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Accelerator, KernelContainer> KernelCache = new();
|
||||
|
||||
private static KernelContainer GetKernels(Accelerator accelerator)
|
||||
{
|
||||
return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer());
|
||||
}
|
||||
|
||||
// Ensures the small cycles table is uploaded to the device for the given accelerator.
|
||||
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
|
||||
public static ArrayView1D<uint, 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.
|
||||
lock (kernels)
|
||||
{
|
||||
if (kernels.SmallCycles is { } existing)
|
||||
{
|
||||
return existing.View;
|
||||
}
|
||||
|
||||
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
var device = accelerator.Allocate1D<uint>(host.Length);
|
||||
device.View.CopyFromCPU(host);
|
||||
kernels.SmallCycles = device;
|
||||
return device.View;
|
||||
}
|
||||
}
|
||||
|
||||
public static GpuKernelLease GetKernel(bool useGpuOrder)
|
||||
{
|
||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
var gpu = RentPreferred(preferCpu: !useGpuOrder);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernels = GetKernels(accelerator);
|
||||
return new(limiter, 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)
|
||||
{
|
||||
using var lease = GetKernel(useGpuOrder: true);
|
||||
var accelerator = lease.Accelerator;
|
||||
using var stream = accelerator.CreateStream();
|
||||
action(accelerator, stream);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public static class GpuPrimeWorkLimiter
|
||||
{
|
||||
private static SemaphoreSlim _semaphore = new(1, int.MaxValue);
|
||||
private static readonly object _lock = new();
|
||||
private static int _currentLimit = 1;
|
||||
|
||||
public static IDisposable Acquire()
|
||||
{
|
||||
var sem = _semaphore;
|
||||
sem.Wait();
|
||||
return new Releaser(sem);
|
||||
}
|
||||
|
||||
public static void SetLimit(int value)
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
value = 1;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (value == _currentLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_semaphore = new SemaphoreSlim(value, value);
|
||||
_currentLimit = value;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Releaser : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _sem;
|
||||
private bool _disposed;
|
||||
|
||||
public Releaser(SemaphoreSlim sem)
|
||||
{
|
||||
_sem = sem;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_sem.Release();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,991 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public readonly struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
{
|
||||
public readonly ulong High;
|
||||
|
||||
public readonly ulong Low;
|
||||
|
||||
public static readonly GpuUInt128 Zero = new(0UL, 0UL);
|
||||
public static readonly GpuUInt128 One = new(0UL, 1UL);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128(ulong high, ulong low)
|
||||
{
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128(UInt128 value)
|
||||
{
|
||||
High = (ulong)(value >> 64);
|
||||
Low = (ulong)value;
|
||||
}
|
||||
|
||||
public readonly bool IsZero
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => High == 0UL && Low == 0UL;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator GpuUInt128(UInt128 value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator GpuUInt128(ulong value) => new(0UL, value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator UInt128(GpuUInt128 value) =>
|
||||
((UInt128)value.High << 64) | value.Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator +(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
var low = left.Low + right.Low;
|
||||
var high = left.High + right.High + (low < left.Low ? 1UL : 0UL);
|
||||
return new(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator -(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
var borrow = left.Low < right.Low ? 1UL : 0UL;
|
||||
var low = left.Low - right.Low;
|
||||
var high = left.High - right.High - borrow;
|
||||
return new(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator ^(GpuUInt128 left, GpuUInt128 right) =>
|
||||
new(left.High ^ right.High, left.Low ^ right.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator *(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
Multiply(left, right, out var high, out var low);
|
||||
return new(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator <<(GpuUInt128 value, int shift)
|
||||
{
|
||||
shift &= 127;
|
||||
if (shift == 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (shift >= 64)
|
||||
{
|
||||
ulong high = value.Low << (shift - 64);
|
||||
return new(high, 0UL);
|
||||
}
|
||||
|
||||
ulong highPart = (value.High << shift) | (value.Low >> (64 - shift));
|
||||
ulong lowPart = value.Low << shift;
|
||||
return new(highPart, lowPart);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator >>(GpuUInt128 value, int shift)
|
||||
{
|
||||
shift &= 127;
|
||||
if (shift == 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (shift >= 64)
|
||||
{
|
||||
ulong low = value.High >> (shift - 64);
|
||||
return new(0UL, low);
|
||||
}
|
||||
|
||||
ulong highPart = value.High >> shift;
|
||||
ulong lowPart = (value.Low >> shift) | (value.High << (64 - shift));
|
||||
return new(highPart, lowPart);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(GpuUInt128 left, GpuUInt128 right) => left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(GpuUInt128 left, GpuUInt128 right) => !left.Equals(right);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator <(GpuUInt128 left, GpuUInt128 right) => left.CompareTo(right) < 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator >(GpuUInt128 left, GpuUInt128 right) => left.CompareTo(right) > 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator <=(GpuUInt128 left, GpuUInt128 right) => left.CompareTo(right) <= 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator >=(GpuUInt128 left, GpuUInt128 right) => left.CompareTo(right) >= 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator ++(GpuUInt128 value) => value.Add(1UL);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int CompareTo(GpuUInt128 other)
|
||||
{
|
||||
if (High < other.High)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (High > other.High)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Low < other.Low)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Low > other.Low)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Equals(GpuUInt128 other) => High == other.High && Low == other.Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override readonly bool Equals(object? obj) => obj is GpuUInt128 value && Equals(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override readonly int GetHashCode() => HashCode.Combine(High, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 Add(GpuUInt128 other)
|
||||
{
|
||||
ulong low = Low + other.Low;
|
||||
return new(High + other.High + (low < Low ? 1UL : 0UL), low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 Add(ulong value)
|
||||
{
|
||||
var low = Low + value;
|
||||
return new(High + (low < Low ? 1UL : 0UL), low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 AddMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
return AddMod(value, modulus.Low);
|
||||
}
|
||||
|
||||
value = Add(value);
|
||||
if (value.CompareTo(modulus) >= 0)
|
||||
{
|
||||
value = value.Sub(modulus);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 AddMod(ulong value, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
return AddMod(value, modulus.Low);
|
||||
}
|
||||
|
||||
var result = Add(value);
|
||||
if (result.CompareTo(modulus) >= 0)
|
||||
{
|
||||
result = result.Sub(modulus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 AddMod(GpuUInt128 value, ulong modulus)
|
||||
{
|
||||
ulong a = Low % modulus;
|
||||
ulong b = value.Low % modulus;
|
||||
ulong sum = a + b;
|
||||
if (sum >= modulus || sum < a)
|
||||
{
|
||||
sum -= modulus;
|
||||
}
|
||||
|
||||
return new(0UL, sum);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 AddMod(ulong value, ulong modulus)
|
||||
{
|
||||
ulong a = Low % modulus;
|
||||
ulong b = value % modulus;
|
||||
ulong sum = a + b;
|
||||
if (sum >= modulus || sum < a)
|
||||
{
|
||||
sum -= modulus;
|
||||
}
|
||||
|
||||
return new(0UL, sum);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 Sub(GpuUInt128 other)
|
||||
{
|
||||
ulong borrow = Low < other.Low ? 1UL : 0UL;
|
||||
return new(High - other.High + borrow, Low - other.Low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong MulHigh(ulong x, ulong y)
|
||||
{
|
||||
ulong xLow = (uint)x;
|
||||
ulong xHigh = x >> 32;
|
||||
ulong yLow = (uint)y;
|
||||
ulong yHigh = y >> 32;
|
||||
|
||||
ulong w1 = xLow * yHigh;
|
||||
ulong w2 = xHigh * yLow;
|
||||
ulong carry =
|
||||
(((xLow * yLow) >> 32) +
|
||||
(uint)w1 +
|
||||
(uint)w2) >> 32;
|
||||
return (xHigh * yHigh) + (w1 >> 32) + (w2 >> 32) + carry;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly GpuUInt128 Mul64(GpuUInt128 other)
|
||||
{
|
||||
// Multiply this.Low (assumed 64-bit value) by full 128-bit other
|
||||
ulong a = Low;
|
||||
ulong low = a * other.Low;
|
||||
ulong high = a * other.High + MulHigh(a, other.Low);
|
||||
return new(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 AddMod(GpuUInt128 a, GpuUInt128 b, GpuUInt128 modulus)
|
||||
{
|
||||
a = a.Add(b);
|
||||
if (a >= modulus)
|
||||
{
|
||||
a = a.Sub(modulus);
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 MulMod(GpuUInt128 a, GpuUInt128 b, GpuUInt128 modulus)
|
||||
{
|
||||
GpuUInt128 result = new(0UL, 0UL);
|
||||
while (!b.IsZero)
|
||||
{
|
||||
if ((b.Low & 1UL) != 0UL)
|
||||
{
|
||||
result = AddMod(result, a, modulus);
|
||||
}
|
||||
|
||||
// a = (a << 1) % modulus
|
||||
a <<= 1;
|
||||
if (a >= modulus)
|
||||
{
|
||||
a = a.Sub(modulus);
|
||||
}
|
||||
|
||||
// b >>= 1
|
||||
if (b.High == 0UL)
|
||||
{
|
||||
b = new GpuUInt128(0UL, b.Low >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong low = (b.Low >> 1) | (b.High << 63);
|
||||
ulong high = b.High >> 1;
|
||||
b = new GpuUInt128(high, low);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 Pow2Mod(ulong exponent, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.IsZero || modulus == One)
|
||||
{
|
||||
return new(0UL, 0UL);
|
||||
}
|
||||
|
||||
GpuUInt128 result = new(0UL, 1UL);
|
||||
GpuUInt128 baseVal = new(0UL, 2UL);
|
||||
|
||||
ulong e = exponent;
|
||||
while (e != 0UL)
|
||||
{
|
||||
if ((e & 1UL) != 0UL)
|
||||
{
|
||||
result = MulMod(result, baseVal, modulus);
|
||||
}
|
||||
|
||||
baseVal = MulMod(baseVal, baseVal, modulus);
|
||||
e >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int TrailingZeroCount(GpuUInt128 value)
|
||||
{
|
||||
if (value.Low != 0UL)
|
||||
{
|
||||
return BitOperations.TrailingZeroCount(value.Low);
|
||||
}
|
||||
|
||||
return 64 + BitOperations.TrailingZeroCount(value.High);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 BinaryGcd(GpuUInt128 u, GpuUInt128 v)
|
||||
{
|
||||
if (u.IsZero)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
if (v.IsZero)
|
||||
{
|
||||
return u;
|
||||
}
|
||||
|
||||
int shift = TrailingZeroCount(new GpuUInt128(u.High | v.High, u.Low | v.Low));
|
||||
// remove factors of 2
|
||||
int zu = TrailingZeroCount(u);
|
||||
u >>= zu;
|
||||
|
||||
do
|
||||
{
|
||||
int zv = TrailingZeroCount(v);
|
||||
v >>= zv;
|
||||
if (u > v)
|
||||
{
|
||||
(u, v) = (v, u);
|
||||
}
|
||||
|
||||
v -= u;
|
||||
}
|
||||
while (!v.IsZero);
|
||||
|
||||
return u << shift;
|
||||
}
|
||||
|
||||
|
||||
public GpuUInt128 Sub(ulong value)
|
||||
{
|
||||
ulong borrow = Low < value ? 1UL : 0UL;
|
||||
return new(High - borrow, Low - value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 SubMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||
{
|
||||
if (CompareTo(value) >= 0)
|
||||
{
|
||||
return Sub(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Add(modulus).Sub(value);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 SubMod(ulong value, GpuUInt128 modulus)
|
||||
{
|
||||
ulong low, high;
|
||||
if (High == 0UL && Low < value)
|
||||
{
|
||||
low = Low + modulus.Low;
|
||||
ulong carry = low < Low ? 1UL : 0UL;
|
||||
high = High + modulus.High + carry;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = High;
|
||||
low = Low;
|
||||
}
|
||||
|
||||
ulong borrow = low < value ? 1UL : 0UL;
|
||||
return new (high - borrow, low - value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 Mul(GpuUInt128 other)
|
||||
{
|
||||
Multiply(this, other, out var high, out var low);
|
||||
return new(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 Mul(ulong value)
|
||||
{
|
||||
var (highPart, lowPart) = Mul64(Low, value);
|
||||
return new(highPart + High * value, lowPart);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 Xor(GpuUInt128 other) => new(High ^ other.High, Low ^ other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 Xor(ulong value) => new(High, Low ^ value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 ShiftLeft(int shift) => this << shift;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 ShiftRight(int shift) => this >> shift;
|
||||
|
||||
public GpuUInt128 MulMod(GpuUInt128 other, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
return MulMod(other, modulus.Low);
|
||||
}
|
||||
|
||||
MultiplyFull(this, other, out var p3, out var p2, out var p1, out var p0);
|
||||
|
||||
GpuUInt128 remainder = new(0UL, 0UL);
|
||||
int bit;
|
||||
for (bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder = new(remainder.High, remainder.Low | ((p3 >> bit) & 1UL));
|
||||
if (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
for (bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder = new(remainder.High, remainder.Low | ((p2 >> bit) & 1UL));
|
||||
if (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
for (bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder = new(remainder.High, remainder.Low | ((p1 >> bit) & 1UL));
|
||||
if (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
for (bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder = new(remainder.High, remainder.Low | ((p0 >> bit) & 1UL));
|
||||
if (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
return remainder;
|
||||
// TODO(MOD-OPT): Replace this bitwise long-division style reduction
|
||||
// with a faster algorithm suitable for GPU kernels without '%' support.
|
||||
// Options:
|
||||
// - Montgomery reduction for 128-bit moduli (R=2^128), requires pre-
|
||||
// computed modulus-dependent constants (n' and R^2 mod n). This is
|
||||
// ideal for NTT primes (odd modulus). Cache per-modulus constants.
|
||||
// - Barrett reduction with a 256/128 quotient approximation using only
|
||||
// multiplies and shifts. Cache mu = floor(2^k / n) for k=256.
|
||||
// Implement a fast path for common 64-bit NTT moduli and a separate
|
||||
// path for 128-bit moduli. This will significantly reduce the cost of
|
||||
// each butterfly in NTT and LL steps.
|
||||
// TODO(MOD-OPT): Plumb constants through caches in NttGpuMath.SquareCacheEntry
|
||||
// (e.g., Montgomery n', R2) and provide device-friendly accessors.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modular multiplication using <see cref="BigInteger"/> reduction. This
|
||||
/// method is intended for validation only and should not be used inside
|
||||
/// GPU kernels.
|
||||
/// </summary>
|
||||
internal GpuUInt128 MulModBigInteger(GpuUInt128 other, GpuUInt128 modulus)
|
||||
{
|
||||
var left = (BigInteger)(UInt128)this;
|
||||
var right = (BigInteger)(UInt128)other;
|
||||
var mod = (BigInteger)(UInt128)modulus;
|
||||
var reduced = (UInt128)((left * right) % mod);
|
||||
return new((ulong)(reduced >> 64), (ulong)reduced);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Experimental limb-based reduction. The current implementation performs
|
||||
/// repeated subtractions and becomes extremely slow for large remainders.
|
||||
/// Kept for future optimization work.
|
||||
/// </summary>
|
||||
internal GpuUInt128 MulModByLimb(GpuUInt128 other, GpuUInt128 modulus)
|
||||
{
|
||||
MultiplyFull(this, other, out var p3, out var p2, out var p1, out var p0);
|
||||
|
||||
GpuUInt128 remainder = new(p3, p2);
|
||||
while (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
|
||||
ulong limb = p1;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
remainder <<= 64;
|
||||
remainder = new(remainder.High, limb);
|
||||
while (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
|
||||
limb = p0;
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 MulMod(ulong value, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
return MulMod(value, modulus.Low);
|
||||
}
|
||||
|
||||
GpuUInt128 other = new(0UL, value);
|
||||
return MulMod(other, modulus);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 MulMod(GpuUInt128 other, ulong modulus)
|
||||
{
|
||||
// Fast-path: use single 64-bit multiply when it cannot overflow
|
||||
// (enabled by ILGPU support for 64-bit % in kernels). Otherwise fall
|
||||
// back to the generic branchless double-and-add implementation.
|
||||
ulong a = Low % modulus;
|
||||
ulong b = other.Low % modulus;
|
||||
ulong result;
|
||||
if (a == 0UL || b == 0UL)
|
||||
{
|
||||
result = 0UL;
|
||||
}
|
||||
else if (b <= ulong.MaxValue / a)
|
||||
{
|
||||
// Safe to multiply without overflow
|
||||
result = (a * b) % modulus;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = MulMod64(a, b, modulus);
|
||||
}
|
||||
|
||||
return new(0UL, result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 MulMod(ulong value, ulong modulus)
|
||||
{
|
||||
// Same fast-path as above for 64-bit operands
|
||||
ulong a = Low % modulus;
|
||||
ulong b = value % modulus;
|
||||
ulong result;
|
||||
if (a == 0UL || b == 0UL)
|
||||
{
|
||||
result = 0UL;
|
||||
}
|
||||
else if (b <= ulong.MaxValue / a)
|
||||
{
|
||||
result = (a * b) % modulus;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = MulMod64(a, b, modulus);
|
||||
}
|
||||
|
||||
return new(0UL, result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 MulModMontgomery64(GpuUInt128 other, ulong modulus, ulong nPrime, ulong r2)
|
||||
{
|
||||
// Montgomery-based 64-bit modular multiplication without division.
|
||||
// Computes (this * other) % modulus using:
|
||||
// aR = MontMul(a, R^2), bR = MontMul(b, R^2), cR = MontMul(aR, bR), c = MontMul(cR, 1)
|
||||
// where R = 2^64, nPrime = -mod^{-1} mod 2^64, r2 = R^2 mod mod.
|
||||
ulong a = Low % modulus;
|
||||
ulong b = other.Low % modulus;
|
||||
if (a == 0UL || b == 0UL)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
|
||||
ulong aR = MontMul64(a, r2, modulus, nPrime);
|
||||
ulong bR = MontMul64(b, r2, modulus, nPrime);
|
||||
ulong cR = MontMul64(aR, bR, modulus, nPrime);
|
||||
ulong c = MontMul64(cR, 1UL, modulus, nPrime);
|
||||
return new(0UL, c);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 MulModMontgomery64(ulong other, ulong modulus, ulong nPrime, ulong r2)
|
||||
{
|
||||
ulong a = Low % modulus;
|
||||
ulong b = other % modulus;
|
||||
if (a == 0UL || b == 0UL)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
|
||||
ulong aR = MontMul64(a, r2, modulus, nPrime);
|
||||
ulong bR = MontMul64(b, r2, modulus, nPrime);
|
||||
ulong cR = MontMul64(aR, bR, modulus, nPrime);
|
||||
ulong c = MontMul64(cR, 1UL, modulus, nPrime);
|
||||
return new(0UL, c);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 SquareMod(GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
// Use fast 64-bit path.
|
||||
return MulMod(Low, modulus.Low);
|
||||
}
|
||||
|
||||
// Specialized 128-bit squaring with bitwise reduction (faster than generic multiply).
|
||||
SquareFull(this, out var p3, out var p2, out var p1, out var p0);
|
||||
|
||||
GpuUInt128 remainder = new(0UL, 0UL);
|
||||
int bit;
|
||||
for (bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder = new(remainder.High, remainder.Low | ((p3 >> bit) & 1UL));
|
||||
if (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
for (bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder = new(remainder.High, remainder.Low | ((p2 >> bit) & 1UL));
|
||||
if (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
for (bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder = new(remainder.High, remainder.Low | ((p1 >> bit) & 1UL));
|
||||
if (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
for (bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder = new(remainder.High, remainder.Low | ((p0 >> bit) & 1UL));
|
||||
if (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder = remainder.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
public GpuUInt128 ModPow(GpuUInt128 exponent, GpuUInt128 modulus)
|
||||
{
|
||||
GpuUInt128 result = new(0UL, 1UL);
|
||||
GpuUInt128 baseValue = this;
|
||||
|
||||
while (!exponent.IsZero)
|
||||
{
|
||||
if ((exponent.Low & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MulMod(baseValue, modulus);
|
||||
}
|
||||
|
||||
exponent >>= 1;
|
||||
baseValue = baseValue.MulMod(baseValue, modulus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public GpuUInt128 ModPow(ulong exponent, GpuUInt128 modulus)
|
||||
{
|
||||
GpuUInt128 result = new(0UL, 1UL);
|
||||
GpuUInt128 baseValue = this;
|
||||
|
||||
while (exponent != 0UL)
|
||||
{
|
||||
if ((exponent & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MulMod(baseValue, modulus);
|
||||
}
|
||||
|
||||
exponent >>= 1;
|
||||
baseValue = baseValue.MulMod(baseValue, modulus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 ModInv(ulong modulus) => ModPow(modulus - 2UL, new GpuUInt128(0UL, modulus));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 ModInv(GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
return ModInv(modulus.Low);
|
||||
}
|
||||
|
||||
var exponent = new GpuUInt128(modulus).Sub(2UL);
|
||||
return ModPow(exponent, modulus);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override readonly string ToString() =>
|
||||
((UInt128)this).ToString();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void MultiplyFull(GpuUInt128 left, GpuUInt128 right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
var (h0, l0) = Mul64(left.Low, right.Low);
|
||||
var (h1, l1) = Mul64(left.Low, right.High);
|
||||
var (h2, l2) = Mul64(left.High, right.Low);
|
||||
var (h3, l3) = Mul64(left.High, right.High);
|
||||
|
||||
p0 = l0;
|
||||
ulong carry = 0UL;
|
||||
ulong sum = h0;
|
||||
sum += l1;
|
||||
if (sum < l1)
|
||||
{
|
||||
carry++;
|
||||
}
|
||||
|
||||
sum += l2;
|
||||
if (sum < l2)
|
||||
{
|
||||
carry++;
|
||||
}
|
||||
|
||||
p1 = sum;
|
||||
sum = h1;
|
||||
sum += h2;
|
||||
ulong carry2 = sum < h2 ? 1UL : 0UL;
|
||||
sum += l3;
|
||||
if (sum < l3)
|
||||
{
|
||||
carry2++;
|
||||
}
|
||||
|
||||
sum += carry;
|
||||
if (sum < carry)
|
||||
{
|
||||
carry2++;
|
||||
}
|
||||
|
||||
p2 = sum;
|
||||
p3 = h3 + carry2;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Multiply(GpuUInt128 left, GpuUInt128 right, out ulong high, out ulong low)
|
||||
{
|
||||
var (h0, l0) = Mul64(left.Low, right.Low);
|
||||
var (_, l1) = Mul64(left.Low, right.High);
|
||||
var (_, l2) = Mul64(left.High, right.Low);
|
||||
|
||||
low = l0;
|
||||
high = h0 + l1 + l2;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static (ulong High, ulong Low) Mul64(ulong left, ulong right)
|
||||
{
|
||||
ulong a0 = (uint)left;
|
||||
ulong a1 = left >> 32;
|
||||
ulong b0 = (uint)right;
|
||||
ulong b1 = right >> 32;
|
||||
|
||||
ulong lo = a0 * b0;
|
||||
ulong mid1 = a1 * b0;
|
||||
ulong mid2 = a0 * b1;
|
||||
ulong hi = a1 * b1;
|
||||
|
||||
ulong carry = (lo >> 32) + (uint)mid1 + (uint)mid2;
|
||||
ulong low = (lo & 0xFFFFFFFFUL) | (carry << 32);
|
||||
hi += (mid1 >> 32) + (mid2 >> 32) + (carry >> 32);
|
||||
|
||||
return (hi, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void SquareFull(GpuUInt128 value, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
// Compute (H*2^64 + L)^2 = H^2*2^128 + 2*H*L*2^64 + L^2
|
||||
ulong L = value.Low;
|
||||
ulong H = value.High;
|
||||
var (hLL, lLL) = Mul64(L, L); // L^2 -> (hLL,lLL)
|
||||
var (hHH, lHH) = Mul64(H, H); // H^2 -> (hHH,lHH)
|
||||
var (hLH, lLH) = Mul64(L, H); // L*H -> (hLH,lLH)
|
||||
|
||||
// double LH: (hLH,lLH) << 1
|
||||
ulong dLH_low = lLH << 1;
|
||||
ulong carry = (lLH >> 63) & 1UL;
|
||||
ulong dLH_high = (hLH << 1) | carry;
|
||||
|
||||
// Assemble 256-bit result parts
|
||||
p0 = lLL;
|
||||
|
||||
// p1 = hLL + dLH_low (with carry)
|
||||
ulong sum1 = hLL + dLH_low;
|
||||
ulong c1 = sum1 < hLL ? 1UL : 0UL;
|
||||
p1 = sum1;
|
||||
|
||||
// p2 = lHH + dLH_high + c1
|
||||
ulong sum2 = lHH + dLH_high;
|
||||
ulong c2 = sum2 < lHH ? 1UL : 0UL;
|
||||
sum2 += c1;
|
||||
if (sum2 < c1)
|
||||
{
|
||||
c2++;
|
||||
}
|
||||
p2 = sum2;
|
||||
|
||||
// p3 = hHH + c2
|
||||
p3 = hHH + c2;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong MulMod64(ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
// Note: 64-bit % is supported in ILGPU kernels, but the straightforward
|
||||
// (UInt128)a*b % modulus is not, because 128-bit multiply is not
|
||||
// supported in device code. We therefore implement a branch-free
|
||||
// shift-add reduction here based solely on 64-bit ops.
|
||||
ulong result = 0UL;
|
||||
ulong x = a % modulus;
|
||||
ulong y = b;
|
||||
while (y != 0UL)
|
||||
{
|
||||
if ((y & 1UL) != 0UL)
|
||||
{
|
||||
result += x;
|
||||
if (result >= modulus)
|
||||
{
|
||||
result -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
x <<= 1;
|
||||
if (x >= modulus)
|
||||
{
|
||||
x -= modulus;
|
||||
}
|
||||
|
||||
y >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Montgomery core for 64-bit operands. Returns a*b*R^{-1} mod modulus.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong MontMul64(ulong aR, ulong bR, ulong modulus, ulong nPrime)
|
||||
{
|
||||
// t = aR * bR (128-bit)
|
||||
ulong tLow, tHigh;
|
||||
Mul64Parts(aR, bR, out tHigh, out tLow);
|
||||
// m = (tLow * nPrime) mod 2^64 (low 64 bits only)
|
||||
ulong mLow, mHigh;
|
||||
Mul64Parts(tLow, nPrime, out mHigh, out mLow);
|
||||
// u = (t + m * modulus) >> 64
|
||||
ulong mmLow, mmHigh;
|
||||
Mul64Parts(mLow, modulus, out mmHigh, out mmLow);
|
||||
ulong carry = 0UL;
|
||||
ulong low = tLow + mmLow;
|
||||
if (low < tLow)
|
||||
{
|
||||
carry = 1UL;
|
||||
}
|
||||
|
||||
ulong high = tHigh + mmHigh + carry;
|
||||
ulong u = high; // (t + m*n) >> 64
|
||||
if (u >= modulus)
|
||||
{
|
||||
u -= modulus;
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
// 64x64 -> 128 multiply into (high, low)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Mul64Parts(ulong a, ulong b, out ulong high, out ulong low)
|
||||
{
|
||||
ulong a0 = (uint)a;
|
||||
ulong a1 = a >> 32;
|
||||
ulong b0 = (uint)b;
|
||||
ulong b1 = b >> 32;
|
||||
|
||||
ulong lo = a0 * b0;
|
||||
ulong mid1 = a1 * b0;
|
||||
ulong mid2 = a0 * b1;
|
||||
ulong hi = a1 * b1;
|
||||
|
||||
ulong carry = (lo >> 32) + (uint)mid1 + (uint)mid2;
|
||||
low = (lo & 0xFFFFFFFFUL) | (carry << 32);
|
||||
hi += (mid1 >> 32) + (mid2 >> 32) + (carry >> 32);
|
||||
high = hi;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(MOD-OPT): Montgomery/Barrett integration plan
|
||||
// - Introduce caches of modulus-dependent constants:
|
||||
// * Montgomery: n' (-(n^{-1}) mod 2^64 or 2^128), R2 = (R^2 mod n)
|
||||
// * Barrett: mu = floor(2^k / n) for k ∈ {128, 192, 256}
|
||||
// - Add fast-path for 64-bit NTT primes (modulus.High == 0UL) using pure 64-bit ops.
|
||||
// - Expose helpers to retrieve/calc constants once per modulus and reuse in kernels.
|
||||
// - Wire these into MulMod and SquareMod hot paths under feature toggles.
|
||||
// - Ensure ILGPU compatibility (no BigInteger, no % inside kernels).
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public static class GpuWorkLimiter
|
||||
{
|
||||
private static SemaphoreSlim _semaphore = new(1, int.MaxValue);
|
||||
private static readonly object _lock = new();
|
||||
private static int _currentLimit = 1;
|
||||
|
||||
public static IDisposable Acquire()
|
||||
{
|
||||
var sem = _semaphore;
|
||||
sem.Wait();
|
||||
return new Releaser(sem);
|
||||
}
|
||||
|
||||
public static void SetLimit(int value)
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
value = 1;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (value == _currentLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap to a new semaphore for the new limit. We intentionally
|
||||
// do not dispose the old semaphore to avoid releasing handles
|
||||
// that may still be in use by existing Releaser instances.
|
||||
_semaphore = new SemaphoreSlim(value, value);
|
||||
_currentLimit = value;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Releaser : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _sem;
|
||||
private bool _disposed;
|
||||
|
||||
public Releaser(SemaphoreSlim sem)
|
||||
{
|
||||
_sem = sem;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_sem.Release();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool useGpuOrder)
|
||||
{
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
private readonly bool _useGpuOrder = useGpuOrder;
|
||||
|
||||
public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
using var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
||||
var accelerator = gpuLease.Accelerator;
|
||||
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
|
||||
UInt128 kStart = 1UL;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
|
||||
byte last = lastIsSeven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
|
||||
|
||||
var kernel = (_kernelType == GpuKernelType.Pow2Mod)
|
||||
? gpuLease.Pow2ModKernel
|
||||
: gpuLease.IncrementalKernel;
|
||||
|
||||
using var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
|
||||
// Avoid giant stack allocations that can trigger StackOverflow when batchSize is large.
|
||||
// Rent a reusable array from the shared pool instead.
|
||||
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
|
||||
UInt128 remaining;
|
||||
int currentSize;
|
||||
int i;
|
||||
UInt128 q = UInt128.Zero;
|
||||
try
|
||||
{
|
||||
while (kStart <= maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
remaining = maxK - kStart + UInt128.One;
|
||||
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
|
||||
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
|
||||
// Precompute residue automaton bases for this batch
|
||||
UInt128 q0 = twoP * kStart + UInt128.One;
|
||||
ulong q0m10 = q0.Mod10();
|
||||
ulong q0m8 = q0.Mod8();
|
||||
ulong q0m3 = q0.Mod3();
|
||||
ulong q0m5 = q0.Mod5();
|
||||
ulong step10 = ((exponent % 10UL) << 1) % 10UL;
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong step5 = ((exponent % 5UL) << 1) % 5UL;
|
||||
|
||||
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
kernel(currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
|
||||
q0m10, q0m8, q0m3, q0m5, orderBuffer.View, smallCyclesView);
|
||||
|
||||
accelerator.Synchronize();
|
||||
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
|
||||
q = twoP * kStart + UInt128.One;
|
||||
for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP)
|
||||
{
|
||||
if (orders[i] != 0UL)
|
||||
{
|
||||
if (q.IsPrimeCandidate())
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kStart += (UInt128)currentSize;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(orderArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,864 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public class MersenneNumberLucasLehmerGpuTester
|
||||
{
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>> KernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> AddSmallKernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> SubSmallKernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> ReduceKernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>> IsZeroKernelCache = new();
|
||||
|
||||
private Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetKernel(Accelerator accelerator)
|
||||
{
|
||||
return KernelCache.GetOrAdd(accelerator, accel => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(Kernel));
|
||||
}
|
||||
|
||||
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator) =>
|
||||
AddSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(AddSmallKernel));
|
||||
|
||||
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetSubSmallKernel(Accelerator accelerator) =>
|
||||
SubSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(SubtractSmallKernel));
|
||||
|
||||
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetReduceKernel(Accelerator accelerator) =>
|
||||
ReduceKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(ReduceModMersenneKernel));
|
||||
|
||||
private Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>> GetIsZeroKernel(Accelerator accelerator) =>
|
||||
IsZeroKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>(IsZeroKernel));
|
||||
|
||||
// Configurable LL slice size to keep kernels short. Default 32.
|
||||
public int SliceSize = 32;
|
||||
|
||||
public bool IsMersennePrime(ulong exponent)
|
||||
{
|
||||
// Default to global kernel preference for backward compatibility.
|
||||
bool runOnGpu = !GpuContextPool.ForceCpu;
|
||||
return IsPrime(exponent, runOnGpu);
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong exponent, bool runOnGpu)
|
||||
{
|
||||
// Early rejections aligned with incremental/order sieves, but safe for small p:
|
||||
// - If 3 | p and p != 3, then 7 | M_p -> composite.
|
||||
// - If p ≡ 1 (mod 4) and p shares a factor with (p-1), reject fast.
|
||||
if ((exponent % 3UL) == 0UL && exponent != 3UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
if (exponent >= 128UL)
|
||||
{
|
||||
if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot))
|
||||
{
|
||||
throw new NotSupportedException("NTT parameters for the given exponent are not available.");
|
||||
}
|
||||
|
||||
return IsMersennePrimeNtt(exponent, nttMod, primitiveRoot, runOnGpu);
|
||||
}
|
||||
|
||||
using var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernel = GetKernel(accelerator);
|
||||
var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL);
|
||||
using var buffer = accelerator.Allocate1D<GpuUInt128>(1);
|
||||
kernel(1, exponent, modulus, buffer.View);
|
||||
accelerator.Synchronize();
|
||||
return buffer.GetAsArray1D()[0].IsZero;
|
||||
}
|
||||
|
||||
private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu)
|
||||
{
|
||||
using var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var addKernel = GetAddSmallKernel(accelerator);
|
||||
var subKernel = GetSubSmallKernel(accelerator);
|
||||
var reduceKernel = GetReduceKernel(accelerator);
|
||||
var zeroKernel = GetIsZeroKernel(accelerator);
|
||||
|
||||
int limbCount = (int)((exponent + 127UL) / 128UL);
|
||||
int target = limbCount * 2;
|
||||
int transformLength = 1;
|
||||
while (transformLength < target)
|
||||
{
|
||||
transformLength <<= 1;
|
||||
}
|
||||
using var stateBuffer = accelerator.Allocate1D<GpuUInt128>(transformLength);
|
||||
stateBuffer.MemSetToZero();
|
||||
addKernel(1, 4UL, stateBuffer.View);
|
||||
|
||||
// TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to
|
||||
// avoid long-running kernels and TDR. Example: process 8–64 iterations
|
||||
// per slice, with synchronization between slices. Combine this with
|
||||
// stage-wise NTT to ensure each kernel stays < ~0.5–1.0s.
|
||||
ulong i = 0UL;
|
||||
ulong limit = exponent - 2UL;
|
||||
int slice = Math.Max(1, SliceSize); // process LL iterations in small slices
|
||||
while (i < limit)
|
||||
{
|
||||
int iter = (int)Math.Min((ulong)slice, limit - i);
|
||||
for (int s = 0; s < iter; s++)
|
||||
{
|
||||
// Square in NTT domain with staged kernels (short runtime per kernel).
|
||||
NttGpuMath.SquareDevice(accelerator, stateBuffer.View, nttMod, primitiveRoot);
|
||||
subKernel(1, 2UL, stateBuffer.View);
|
||||
reduceKernel(1, exponent, stateBuffer.View);
|
||||
}
|
||||
|
||||
// Synchronize between slices to yield to scheduler and avoid TDR.
|
||||
accelerator.Synchronize();
|
||||
i += (ulong)iter;
|
||||
}
|
||||
|
||||
reduceKernel(1, exponent, stateBuffer.View);
|
||||
|
||||
using var resultBuffer = accelerator.Allocate1D<byte>(1);
|
||||
zeroKernel(1, stateBuffer.View, resultBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
byte[] result = new byte[1];
|
||||
resultBuffer.View.CopyToCPU(result);
|
||||
return result[0] != 0;
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<int, (GpuUInt128 Modulus, GpuUInt128 PrimitiveRoot)> NttParameterCache = new()
|
||||
{
|
||||
// 2^12 transform length using a 64-bit NTT-friendly prime
|
||||
[4096] = (new GpuUInt128(0UL, 18446744069414584321UL), new GpuUInt128(0UL, 7UL)),
|
||||
// 2^22 transform length precomputed for p ~= 138,000,000
|
||||
[4194304] = (new GpuUInt128(0UL, 104857601UL), new GpuUInt128(0UL, 39193363UL))
|
||||
};
|
||||
|
||||
private readonly object ParameterFileLock = new();
|
||||
private readonly string ParameterFilePath = Path.Combine(AppContext.BaseDirectory, "ntt-params.txt");
|
||||
|
||||
public void WarmUpNttParameters(ulong exponent)
|
||||
{
|
||||
_ = TryGetNttParameters(exponent, out _, out _);
|
||||
}
|
||||
|
||||
private bool TryGetNttParameters(ulong exponent, out GpuUInt128 modulus, out GpuUInt128 primitiveRoot)
|
||||
{
|
||||
int limbCount = (int)((exponent + 127UL) / 128UL);
|
||||
int length = 1;
|
||||
int target = limbCount * 2;
|
||||
while (length < target)
|
||||
{
|
||||
length <<= 1;
|
||||
}
|
||||
|
||||
if (NttParameterCache.TryGetValue(length, out var cached))
|
||||
{
|
||||
modulus = cached.Modulus;
|
||||
primitiveRoot = cached.PrimitiveRoot;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryLoadPersistedParameters(length, out modulus, out primitiveRoot))
|
||||
{
|
||||
NttParameterCache[length] = (modulus, primitiveRoot);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GenerateNttParameters(length, out modulus, out primitiveRoot))
|
||||
{
|
||||
NttParameterCache[length] = (modulus, primitiveRoot);
|
||||
PersistParameters(length, modulus, primitiveRoot);
|
||||
return true;
|
||||
}
|
||||
|
||||
modulus = new GpuUInt128(0UL, 18446744069414584321UL); // 2^64 - 2^32 + 1
|
||||
primitiveRoot = new GpuUInt128(0UL, 7UL);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryLoadPersistedParameters(int length, out GpuUInt128 modulus, out GpuUInt128 primitiveRoot)
|
||||
{
|
||||
if (!File.Exists(ParameterFilePath))
|
||||
{
|
||||
modulus = new GpuUInt128(0UL, 0UL);
|
||||
primitiveRoot = new GpuUInt128(0UL, 0UL);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var line in File.ReadLines(ParameterFilePath))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 5)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!int.TryParse(parts[0], out var storedLength) || storedLength != length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong modHigh = ulong.Parse(parts[1]);
|
||||
ulong modLow = ulong.Parse(parts[2]);
|
||||
ulong rootHigh = ulong.Parse(parts[3]);
|
||||
ulong rootLow = ulong.Parse(parts[4]);
|
||||
modulus = new GpuUInt128(modHigh, modLow);
|
||||
primitiveRoot = new GpuUInt128(rootHigh, rootLow);
|
||||
return true;
|
||||
}
|
||||
|
||||
modulus = new GpuUInt128(0UL, 0UL);
|
||||
primitiveRoot = new GpuUInt128(0UL, 0UL);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void PersistParameters(int length, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
|
||||
{
|
||||
lock (ParameterFileLock)
|
||||
{
|
||||
using var writer = new StreamWriter(ParameterFilePath, append: true);
|
||||
writer.WriteLine($"{length} {modulus.High} {modulus.Low} {primitiveRoot.High} {primitiveRoot.Low}");
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ulong[] SmallPrimes = GenerateSmallPrimes(1000);
|
||||
|
||||
private static ulong[] GenerateSmallPrimes(int limit)
|
||||
{
|
||||
var isComposite = new bool[limit + 1];
|
||||
var primes = new List<ulong>();
|
||||
for (int p = 2; p <= limit; p++)
|
||||
{
|
||||
if (isComposite[p])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
primes.Add((ulong)p);
|
||||
long square = (long)p * p;
|
||||
for (long multiple = square; multiple <= limit; multiple += p)
|
||||
{
|
||||
isComposite[(int)multiple] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return primes.ToArray();
|
||||
}
|
||||
|
||||
private static bool GenerateNttParameters(int length, out GpuUInt128 modulus, out GpuUInt128 primitiveRoot)
|
||||
{
|
||||
ulong foundCandidate = 0UL;
|
||||
ulong foundRoot = 0UL;
|
||||
bool success = false;
|
||||
var processorCount = Environment.ProcessorCount;
|
||||
using var cts = new System.Threading.CancellationTokenSource();
|
||||
object sync = new();
|
||||
|
||||
System.Threading.Tasks.Parallel.For(0, processorCount, (worker, state) =>
|
||||
{
|
||||
ulong k = (ulong)worker + 1UL;
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
ulong candidate;
|
||||
try
|
||||
{
|
||||
candidate = checked((ulong)length * k + 1UL);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bool divisible = false;
|
||||
foreach (ulong p in SmallPrimes)
|
||||
{
|
||||
if (candidate % p == 0UL)
|
||||
{
|
||||
divisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!divisible && IsPrime(candidate))
|
||||
{
|
||||
ulong root = FindPrimitiveRoot(candidate, (ulong)length);
|
||||
lock (sync)
|
||||
{
|
||||
foundCandidate = candidate;
|
||||
foundRoot = root;
|
||||
success = true;
|
||||
cts.Cancel();
|
||||
state.Stop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
k += (ulong)processorCount;
|
||||
}
|
||||
});
|
||||
|
||||
if (success)
|
||||
{
|
||||
modulus = new GpuUInt128(0UL, foundCandidate);
|
||||
primitiveRoot = new GpuUInt128(0UL, foundRoot);
|
||||
return true;
|
||||
}
|
||||
|
||||
modulus = new GpuUInt128(0UL, 18446744069414584321UL);
|
||||
primitiveRoot = new GpuUInt128(0UL, 7UL);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ulong FindPrimitiveRoot(ulong modulus, ulong order)
|
||||
{
|
||||
ulong phi = modulus - 1UL;
|
||||
var factors = Factorize(phi);
|
||||
for (ulong g = 2UL; g < modulus; g++)
|
||||
{
|
||||
bool ok = true;
|
||||
foreach (ulong f in factors)
|
||||
{
|
||||
if (ModPow(g, phi / f, modulus) == 1UL)
|
||||
{
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok)
|
||||
{
|
||||
return ModPow(g, phi / order, modulus);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Primitive root not found.");
|
||||
}
|
||||
|
||||
private static List<ulong> Factorize(ulong n)
|
||||
{
|
||||
var factors = new List<ulong>();
|
||||
for (ulong p = 2UL; p * p <= n; p += p == 2UL ? 1UL : 2UL)
|
||||
{
|
||||
if (n % p == 0UL)
|
||||
{
|
||||
factors.Add(p);
|
||||
while (n % p == 0UL)
|
||||
{
|
||||
n /= p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (n > 1UL)
|
||||
{
|
||||
factors.Add(n);
|
||||
}
|
||||
|
||||
return factors;
|
||||
}
|
||||
|
||||
private static bool IsPrime(ulong n)
|
||||
{
|
||||
if (n < 2UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong[] bases = [2UL, 3UL, 5UL, 7UL, 11UL, 13UL];
|
||||
ulong d = n - 1UL;
|
||||
int s = 0;
|
||||
while ((d & 1UL) == 0UL)
|
||||
{
|
||||
d >>= 1;
|
||||
s++;
|
||||
}
|
||||
|
||||
foreach (ulong a in bases)
|
||||
{
|
||||
if (a >= n)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong x = ModPow(a, d, n);
|
||||
if (x == 1UL || x == n - 1UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool cont = false;
|
||||
for (int r = 1; r < s; r++)
|
||||
{
|
||||
x = MulMod(x, x, n);
|
||||
if (x == n - 1UL)
|
||||
{
|
||||
cont = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cont)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ulong ModPow(ulong value, ulong exponent, ulong modulus)
|
||||
{
|
||||
ulong result = 1UL;
|
||||
while (exponent > 0UL)
|
||||
{
|
||||
if ((exponent & 1UL) != 0UL)
|
||||
{
|
||||
result = MulMod(result, value, modulus);
|
||||
}
|
||||
|
||||
value = MulMod(value, value, modulus);
|
||||
exponent >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ulong MulMod(ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
return (ulong)(((UInt128)a * b) % modulus);
|
||||
}
|
||||
|
||||
private static void AddSmallKernel(Index1D index, ulong add, ArrayView<GpuUInt128> value)
|
||||
{
|
||||
ulong carry = add;
|
||||
for (int i = 0; i < value.Length && carry != 0UL; i++)
|
||||
{
|
||||
var limb = value[i];
|
||||
ulong low = limb.Low + carry;
|
||||
ulong carryOut = low < carry ? 1UL : 0UL;
|
||||
ulong high = limb.High + carryOut;
|
||||
carry = high < limb.High ? 1UL : 0UL;
|
||||
value[i] = new GpuUInt128(high, low);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SubtractSmallKernel(Index1D index, ulong subtract, ArrayView<GpuUInt128> value)
|
||||
{
|
||||
ulong borrow = subtract;
|
||||
for (int i = 0; i < value.Length && borrow != 0UL; i++)
|
||||
{
|
||||
var limb = value[i];
|
||||
ulong low = limb.Low;
|
||||
ulong high = limb.High;
|
||||
if (low >= borrow)
|
||||
{
|
||||
low -= borrow;
|
||||
borrow = 0UL;
|
||||
}
|
||||
else
|
||||
{
|
||||
low = unchecked(low - borrow);
|
||||
borrow = 1UL;
|
||||
if (high != 0UL)
|
||||
{
|
||||
high--;
|
||||
borrow = 0UL;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
value[i] = new GpuUInt128(high, low);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReduceModMersenneKernel(Index1D index, ulong exponent, ArrayView<GpuUInt128> value)
|
||||
{
|
||||
int limbCount = (int)((exponent + 127UL) / 128UL);
|
||||
for (int i = limbCount; i < value.Length; i++)
|
||||
{
|
||||
var limb = value[i];
|
||||
if (limb.IsZero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int target = i - limbCount;
|
||||
var sum = value[target];
|
||||
var original = sum;
|
||||
sum = sum.Add(limb);
|
||||
value[target] = sum;
|
||||
value[i] = new GpuUInt128(0UL, 0UL);
|
||||
if (sum.CompareTo(original) < 0)
|
||||
{
|
||||
int j = target + 1;
|
||||
while (j < limbCount)
|
||||
{
|
||||
var next = value[j];
|
||||
var nextOriginal = next;
|
||||
next = next.Add(1UL);
|
||||
value[j] = next;
|
||||
if (next.CompareTo(nextOriginal) >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int topBits = (int)(exponent % 128UL);
|
||||
if (topBits == 0)
|
||||
{
|
||||
topBits = 128;
|
||||
}
|
||||
|
||||
int topIndex = limbCount - 1;
|
||||
ulong topLowMask = topBits >= 64 ? ulong.MaxValue : (1UL << topBits) - 1UL;
|
||||
ulong topHighMask = topBits > 64 ? (1UL << (topBits - 64)) - 1UL : 0UL;
|
||||
var top = value[topIndex];
|
||||
var carryBits = top >> topBits;
|
||||
top = new(top.High & topHighMask, top.Low & topLowMask);
|
||||
value[topIndex] = top;
|
||||
|
||||
if (!carryBits.IsZero)
|
||||
{
|
||||
int j = 0;
|
||||
while (j < limbCount && !carryBits.IsZero)
|
||||
{
|
||||
var cur = value[j];
|
||||
var curOriginal = cur;
|
||||
cur = cur.Add(carryBits);
|
||||
value[j] = cur;
|
||||
carryBits = cur.CompareTo(curOriginal) < 0 ? new GpuUInt128(0UL, 1UL) : new GpuUInt128(0UL, 0UL);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
bool geq = true;
|
||||
for (int i = topIndex; i >= 0; i--)
|
||||
{
|
||||
var limb = value[i];
|
||||
ulong modHigh = i == topIndex ? topHighMask : ulong.MaxValue;
|
||||
ulong modLow = i == topIndex ? topLowMask : ulong.MaxValue;
|
||||
if (limb.High > modHigh || (limb.High == modHigh && limb.Low > modLow))
|
||||
{
|
||||
geq = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (limb.High < modHigh || (limb.High == modHigh && limb.Low < modLow))
|
||||
{
|
||||
geq = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (geq)
|
||||
{
|
||||
ulong borrow = 0UL;
|
||||
for (int i = 0; i < limbCount - 1; i++)
|
||||
{
|
||||
var limb = value[i];
|
||||
ulong newLow = limb.Low - ulong.MaxValue - borrow;
|
||||
borrow = limb.Low < ulong.MaxValue + borrow ? 1UL : 0UL;
|
||||
ulong newHigh = limb.High - ulong.MaxValue - borrow;
|
||||
borrow = limb.High < ulong.MaxValue + borrow ? 1UL : 0UL;
|
||||
value[i] = new GpuUInt128(newHigh, newLow);
|
||||
}
|
||||
|
||||
var topLimb = value[topIndex];
|
||||
ulong newTopLow = topLimb.Low - topLowMask - borrow;
|
||||
borrow = topLimb.Low < topLowMask + borrow ? 1UL : 0UL;
|
||||
ulong newTopHigh = topLimb.High - topHighMask - borrow;
|
||||
value[topIndex] = new GpuUInt128(newTopHigh, newTopLow);
|
||||
}
|
||||
|
||||
for (int i = limbCount; i < value.Length; i++)
|
||||
{
|
||||
value[i] = new GpuUInt128(0UL, 0UL);
|
||||
}
|
||||
}
|
||||
|
||||
private static void IsZeroKernel(Index1D index, ArrayView<GpuUInt128> value, ArrayView<byte> result)
|
||||
{
|
||||
byte isZero = 1;
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
var limb = value[i];
|
||||
if (limb.High != 0UL || limb.Low != 0UL)
|
||||
{
|
||||
isZero = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result[0] = isZero;
|
||||
}
|
||||
|
||||
private static void ReduceModMersenne(Span<GpuUInt128> value, ulong exponent)
|
||||
{
|
||||
int limbCount = (int)((exponent + 127UL) / 128UL);
|
||||
for (int i = limbCount; i < value.Length; i++)
|
||||
{
|
||||
var limb = value[i];
|
||||
if (limb.IsZero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int target = i - limbCount;
|
||||
UInt128 original = value[target];
|
||||
UInt128 sum = original + (UInt128)limb;
|
||||
bool carry = sum < original;
|
||||
value[target] = new GpuUInt128(sum);
|
||||
value[i] = new GpuUInt128(0UL, 0UL);
|
||||
if (carry)
|
||||
{
|
||||
int j = target + 1;
|
||||
while (j < limbCount)
|
||||
{
|
||||
UInt128 next = value[j];
|
||||
UInt128 nextSum = next + 1UL;
|
||||
value[j] = new GpuUInt128(nextSum);
|
||||
if (nextSum != 0UL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int topBits = (int)(exponent % 128UL);
|
||||
if (topBits == 0)
|
||||
{
|
||||
topBits = 128;
|
||||
}
|
||||
|
||||
int topIndex = limbCount - 1;
|
||||
ulong topLowMask = topBits >= 64 ? ulong.MaxValue : (1UL << topBits) - 1UL;
|
||||
ulong topHighMask = topBits > 64 ? (1UL << (topBits - 64)) - 1UL : 0UL;
|
||||
UInt128 mask = ((UInt128)topHighMask << 64) | topLowMask;
|
||||
UInt128 top = value[topIndex];
|
||||
UInt128 carryBits = top >> topBits;
|
||||
top &= mask;
|
||||
value[topIndex] = new GpuUInt128(top);
|
||||
if (carryBits != 0UL)
|
||||
{
|
||||
int j = 0;
|
||||
UInt128 carry = carryBits;
|
||||
while (j < limbCount && carry != 0UL)
|
||||
{
|
||||
UInt128 cur = value[j];
|
||||
UInt128 sum = cur + carry;
|
||||
bool overflow = sum < cur;
|
||||
value[j] = new GpuUInt128(sum);
|
||||
carry = overflow ? 1UL : 0UL;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
bool geq = true;
|
||||
for (int i = topIndex; i >= 0; i--)
|
||||
{
|
||||
UInt128 limb = value[i];
|
||||
UInt128 modLimb = i == topIndex ? mask : UInt128.MaxValue;
|
||||
if (limb > modLimb)
|
||||
{
|
||||
geq = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (limb < modLimb)
|
||||
{
|
||||
geq = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (geq)
|
||||
{
|
||||
ulong borrow = 0UL;
|
||||
for (int i = 0; i < limbCount - 1; i++)
|
||||
{
|
||||
ulong low = value[i].Low;
|
||||
ulong high = value[i].High;
|
||||
ulong newLow = low - ulong.MaxValue - borrow;
|
||||
borrow = low < ulong.MaxValue + borrow ? 1UL : 0UL;
|
||||
ulong newHigh = high - ulong.MaxValue - borrow;
|
||||
borrow = high < ulong.MaxValue + borrow ? 1UL : 0UL;
|
||||
value[i] = new GpuUInt128(newHigh, newLow);
|
||||
}
|
||||
|
||||
ulong topLow = value[topIndex].Low;
|
||||
ulong topHigh = value[topIndex].High;
|
||||
ulong newTopLow = topLow - topLowMask - borrow;
|
||||
borrow = topLow < topLowMask + borrow ? 1UL : 0UL;
|
||||
ulong newTopHigh = topHigh - topHighMask - borrow;
|
||||
value[topIndex] = new GpuUInt128(newTopHigh, newTopLow);
|
||||
}
|
||||
|
||||
for (int i = limbCount; i < value.Length; i++)
|
||||
{
|
||||
value[i] = new GpuUInt128(0UL, 0UL);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Kernel(Index1D index, ulong exponent, GpuUInt128 modulus, ArrayView<GpuUInt128> state)
|
||||
{
|
||||
// Lucas–Lehmer iteration in the field GF(2^p-1).
|
||||
// For p < 128, use a Mersenne-specific squaring + reduction to avoid
|
||||
// the generic 128-bit long-division-like reduction.
|
||||
var s = new GpuUInt128(0UL, 4UL);
|
||||
ulong i = 0UL, limit = exponent - 2UL;
|
||||
if (exponent < 128UL)
|
||||
{
|
||||
for (; i < limit; i++)
|
||||
{
|
||||
s = SquareModMersenne128(s, exponent).SubMod(2UL, modulus);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; i < limit; i++)
|
||||
{
|
||||
s = s.SquareMod(modulus).SubMod(2UL, modulus);
|
||||
}
|
||||
}
|
||||
|
||||
state[0] = s;
|
||||
}
|
||||
|
||||
// Squares a 128-bit value and reduces modulo M_p = 2^p - 1 using
|
||||
// Mersenne folding. Valid for 0 < p < 128 and input s in [0, M_p].
|
||||
private static GpuUInt128 SquareModMersenne128(GpuUInt128 s, ulong p)
|
||||
{
|
||||
// Full square: (H*2^64 + L)^2 = H^2*2^128 + 2HL*2^64 + L^2
|
||||
SquareFull128(s, out var q3, out var q2, out var q1, out var q0);
|
||||
|
||||
// v = low128 + high128 (fold by 128 bits once)
|
||||
var vHigh = new GpuUInt128(q3, q2);
|
||||
var vLow = new GpuUInt128(q1, q0);
|
||||
vLow = vLow.Add(vHigh);
|
||||
|
||||
// Mask to keep only p low bits and fold the carry bits (v >> p)
|
||||
int topBits = (int)(p % 128UL);
|
||||
if (topBits == 0)
|
||||
{
|
||||
topBits = 128;
|
||||
}
|
||||
|
||||
ulong maskLow = topBits >= 64 ? ulong.MaxValue : (1UL << topBits) - 1UL;
|
||||
ulong maskHigh = topBits > 64 ? (1UL << (topBits - 64)) - 1UL : 0UL;
|
||||
|
||||
var carryBits = vLow >> topBits;
|
||||
vLow = new(vLow.High & maskHigh, vLow.Low & maskLow);
|
||||
|
||||
// Propagate carryBits back into the masked value (single-limb fold)
|
||||
GpuUInt128 before;
|
||||
while (!carryBits.IsZero)
|
||||
{
|
||||
before = vLow;
|
||||
vLow = vLow.Add(carryBits);
|
||||
carryBits = vLow.CompareTo(before) < 0 ? GpuUInt128.One : GpuUInt128.Zero;
|
||||
}
|
||||
|
||||
// Final correction if v >= modulus (mask)
|
||||
if (vLow.High > maskHigh || (vLow.High == maskHigh && vLow.Low >= maskLow))
|
||||
{
|
||||
ulong borrow = 0UL;
|
||||
ulong newLow = vLow.Low - maskLow;
|
||||
borrow = vLow.Low < maskLow ? 1UL : 0UL;
|
||||
ulong newHigh = vLow.High - maskHigh - borrow;
|
||||
vLow = new GpuUInt128(newHigh, newLow);
|
||||
}
|
||||
|
||||
return vLow;
|
||||
}
|
||||
|
||||
// Helper: 128-bit square -> 256-bit product as four 64-bit limbs (q3..q0)
|
||||
private static void SquareFull128(GpuUInt128 value, out ulong q3, out ulong q2, out ulong q1, out ulong q0)
|
||||
{
|
||||
ulong L = value.Low;
|
||||
ulong H = value.High;
|
||||
Mul64Parts(L, L, out var hLL, out var lLL); // L^2
|
||||
Mul64Parts(H, H, out var hHH, out var lHH); // H^2
|
||||
Mul64Parts(L, H, out var hLH, out var lLH); // L*H
|
||||
|
||||
// double LH
|
||||
ulong dLH_low = lLH << 1;
|
||||
ulong carry = (lLH >> 63) & 1UL;
|
||||
ulong dLH_high = (hLH << 1) | carry;
|
||||
|
||||
q0 = lLL;
|
||||
ulong sum1 = hLL + dLH_low;
|
||||
ulong c1 = sum1 < hLL ? 1UL : 0UL;
|
||||
q1 = sum1;
|
||||
|
||||
ulong sum2 = lHH + dLH_high;
|
||||
ulong c2 = sum2 < lHH ? 1UL : 0UL;
|
||||
sum2 += c1;
|
||||
if (sum2 < c1)
|
||||
{
|
||||
c2++;
|
||||
}
|
||||
q2 = sum2;
|
||||
q3 = hHH + c2;
|
||||
}
|
||||
|
||||
// 64x64 -> 128 multiply (high,low)
|
||||
[MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
private static void Mul64Parts(ulong a, ulong b, out ulong high, out ulong low)
|
||||
{
|
||||
ulong a0 = (uint)a;
|
||||
ulong a1 = a >> 32;
|
||||
ulong b0 = (uint)b;
|
||||
ulong b1 = b >> 32;
|
||||
|
||||
ulong lo = a0 * b0;
|
||||
ulong mid1 = a1 * b0;
|
||||
ulong mid2 = a0 * b1;
|
||||
ulong hi = a1 * b1;
|
||||
|
||||
ulong carry = (lo >> 32) + (uint)mid1 + (uint)mid2;
|
||||
low = (lo & 0xFFFFFFFFUL) | (carry << 32);
|
||||
hi += (mid1 >> 32) + (mid2 >> 32) + (carry >> 32);
|
||||
high = hi;
|
||||
}
|
||||
|
||||
[MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ModPow64(ulong value, ulong exponent, ulong modulus)
|
||||
{
|
||||
ulong result = 1UL;
|
||||
ulong baseValue = value % modulus;
|
||||
ulong exp = exponent;
|
||||
|
||||
while (exp != 0UL)
|
||||
{
|
||||
if ((exp & 1UL) != 0UL)
|
||||
{
|
||||
result = (ulong)(((UInt128)result * baseValue) % modulus);
|
||||
}
|
||||
|
||||
baseValue = (ulong)(((UInt128)baseValue * baseValue) % modulus);
|
||||
exp >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuOrder)
|
||||
{
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
private readonly bool _useGpuOrder = useGpuOrder;
|
||||
|
||||
public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
using var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
||||
var accelerator = gpuLease.Accelerator;
|
||||
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
|
||||
UInt128 kStart = UInt128.One;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
|
||||
byte last = lastIsSeven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
|
||||
|
||||
var kernel = _kernelType switch
|
||||
{
|
||||
GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel,
|
||||
_ => gpuLease.IncrementalOrderKernel,
|
||||
};
|
||||
|
||||
using var foundBuffer = accelerator.Allocate1D<int>(1);
|
||||
UInt128 remaining;
|
||||
int currentSize;
|
||||
int found = 0;
|
||||
while (kStart <= maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
remaining = maxK - kStart + 1UL;
|
||||
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
|
||||
foundBuffer.MemSetToZero();
|
||||
// Precompute residue automaton bases for this batch
|
||||
UInt128 q0 = twoP * kStart + 1UL;
|
||||
ulong q0m10 = q0.Mod10();
|
||||
ulong q0m8 = q0.Mod8();
|
||||
ulong q0m3 = q0.Mod3();
|
||||
ulong q0m5 = q0.Mod5();
|
||||
ulong step10 = ((exponent % 10UL) << 1) % 10UL;
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong step5 = ((exponent % 5UL) << 1) % 5UL;
|
||||
var ra = 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(accelerator);
|
||||
kernel(currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
|
||||
ra, foundBuffer.View, smallCyclesView);
|
||||
|
||||
accelerator.Synchronize();
|
||||
foundBuffer.View.CopyToCPU(ref found, 1);
|
||||
if (found != 0)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
|
||||
kStart += (UInt128)currentSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public class MersenneNumberResidueGpuTester(bool useGpuOrder)
|
||||
{
|
||||
private readonly bool _useGpuOrder = useGpuOrder;
|
||||
// TODO: Integrate MersenneDivisorCycles.Shared to consult cycle lengths for small q (<= 4M) and fast-reject
|
||||
// candidates without launching heavy kernels. Consider a device-visible constant buffer per-accelerator
|
||||
// via GpuKernelPool to avoid host round-trips.
|
||||
|
||||
// GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1.
|
||||
public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
using var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
||||
var accelerator = gpuLease.Accelerator;
|
||||
// Ensure device has small cycles table for in-kernel lookup
|
||||
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
int batchSize = GpuConstants.ScanBatchSize;
|
||||
UInt128 kStart = 1UL;
|
||||
byte last = lastIsSeven ? (byte)1 : (byte)0;
|
||||
var kernel = gpuLease.Pow2ModKernel;
|
||||
|
||||
using var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
|
||||
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
|
||||
UInt128 remaining;
|
||||
int currentSize;
|
||||
int i;
|
||||
UInt128 batchSize128 = (UInt128)batchSize,
|
||||
q = UInt128.Zero;
|
||||
Span<ulong> orders = orderArray.AsSpan(0, batchSize);
|
||||
try
|
||||
{
|
||||
while (kStart < maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
remaining = maxK - kStart;
|
||||
if (remaining > batchSize128)
|
||||
{
|
||||
currentSize = batchSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSize = (int)remaining;
|
||||
orders = orderArray.AsSpan(0, currentSize);
|
||||
}
|
||||
|
||||
q = twoP * kStart + UInt128.One;
|
||||
q.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
ulong step10 = ((exponent % 10UL) << 1) % 10UL;
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong step5 = ((exponent % 5UL) << 1) % 5UL;
|
||||
|
||||
kernel(currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, 0UL,
|
||||
q0m10, q0m8, q0m3, q0m5, orderBuffer.View, smallCyclesView);
|
||||
|
||||
accelerator.Synchronize();
|
||||
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
|
||||
if (!Volatile.Read(ref isPrime))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < currentSize; i++, q += twoP)
|
||||
{
|
||||
if (orders[i] != 0UL && q.IsPrimeCandidate())
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
kStart += batchSize128;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(orderArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user