Add GPU options for by-divisor Montgomery data
This commit is contained in:
1 parent
ea29035c55
commit
483584b5d4
5 files changed
+445
-73
No files matched your search
@@ -76,6 +76,7 @@ internal static class Program
|
|||||||
bool useDivisor = false; // M_p divisibility by specific divisor
|
bool useDivisor = false; // M_p divisibility by specific divisor
|
||||||
bool useByDivisor = false; // Iterative divisor scan across primes UInt128 divisor = UInt128.Zero;
|
bool useByDivisor = false; // Iterative divisor scan across primes UInt128 divisor = UInt128.Zero;
|
||||||
ByDivisorDeltasDevice byDivisorDeltasDevice = ByDivisorDeltasDevice.Cpu;
|
ByDivisorDeltasDevice byDivisorDeltasDevice = ByDivisorDeltasDevice.Cpu;
|
||||||
|
ByDivisorMontgomeryDevice byDivisorMontgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
||||||
// Device routing
|
// Device routing
|
||||||
bool useGpuCycles = true;
|
bool useGpuCycles = true;
|
||||||
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
|
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
|
||||||
@@ -200,6 +201,23 @@ internal static class Program
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (arg.StartsWith("--bydivisor-montgomery-device=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> deviceValue = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||||
|
if (deviceValue.Equals("gpu", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
byDivisorMontgomeryDevice = ByDivisorMontgomeryDevice.Gpu;
|
||||||
|
}
|
||||||
|
else if (deviceValue.Equals("cpu", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
byDivisorMontgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Invalid --bydivisor-montgomery-device value. Expected cpu or gpu.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (arg.StartsWith("--prime-test-limit=", StringComparison.OrdinalIgnoreCase))
|
else if (arg.StartsWith("--prime-test-limit=", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||||
@@ -552,6 +570,7 @@ internal static class Program
|
|||||||
if (_byDivisorTester is MersenneNumberDivisorByDivisorCpuTester cpuByDivisorTester)
|
if (_byDivisorTester is MersenneNumberDivisorByDivisorCpuTester cpuByDivisorTester)
|
||||||
{
|
{
|
||||||
cpuByDivisorTester.DeltasDevice = byDivisorDeltasDevice;
|
cpuByDivisorTester.DeltasDevice = byDivisorDeltasDevice;
|
||||||
|
cpuByDivisorTester.MontgomeryDevice = byDivisorMontgomeryDevice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,6 +1026,7 @@ internal static class Program
|
|||||||
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor");
|
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor");
|
||||||
Console.WriteLine(" --prime-test-limit=<value> Time limit for --mersenne=bydivisor prime checks (e.g. 5s, 500ms)");
|
Console.WriteLine(" --prime-test-limit=<value> Time limit for --mersenne=bydivisor prime checks (e.g. 5s, 500ms)");
|
||||||
Console.WriteLine(" --bydivisor-deltas-device=cpu|gpu Device for --mersenne=bydivisor residue batches (default: cpu)");
|
Console.WriteLine(" --bydivisor-deltas-device=cpu|gpu Device for --mersenne=bydivisor residue batches (default: cpu)");
|
||||||
|
Console.WriteLine(" --bydivisor-montgomery-device=cpu|gpu Device for Montgomery data when using --mersenne=bydivisor (default: cpu)");
|
||||||
Console.WriteLine(" --use-order test primality via q order");
|
Console.WriteLine(" --use-order test primality via q order");
|
||||||
Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU");
|
Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU");
|
||||||
// mod-automaton removed
|
// mod-automaton removed
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
public enum ByDivisorMontgomeryDevice
|
||||||
|
{
|
||||||
|
Cpu = 0,
|
||||||
|
Gpu = 1,
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private ulong _configuredMaxPrime;
|
private ulong _configuredMaxPrime;
|
||||||
private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu;
|
private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu;
|
||||||
|
private ByDivisorMontgomeryDevice _montgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
||||||
|
|
||||||
public int BatchSize
|
public int BatchSize
|
||||||
{
|
{
|
||||||
@@ -43,6 +43,18 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ByDivisorMontgomeryDevice MontgomeryDevice
|
||||||
|
{
|
||||||
|
get => _montgomeryDevice;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_montgomeryDevice = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
@@ -402,26 +414,51 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
MontgomeryDivisorData cachedDivisorData = default;
|
MontgomeryDivisorData cachedDivisorData = default;
|
||||||
bool hasCachedDivisorData = false;
|
bool hasCachedDivisorData = false;
|
||||||
|
|
||||||
byte step10 = (byte)(step % 10UL);
|
const int RemainderTableSize = 6;
|
||||||
byte step8 = (byte)(step % 8UL);
|
byte[] remainderBuffer = ArrayPool<byte>.Shared.Rent(RemainderTableSize);
|
||||||
byte step5 = (byte)(step % 5UL);
|
byte[] stepBuffer = ArrayPool<byte>.Shared.Rent(RemainderTableSize);
|
||||||
byte step3 = (byte)(step % 3UL);
|
Span<byte> remainderSpan = remainderBuffer.AsSpan(0, RemainderTableSize);
|
||||||
byte step7 = (byte)(step % 7UL);
|
Span<byte> stepSpan = stepBuffer.AsSpan(0, RemainderTableSize);
|
||||||
byte step11 = (byte)(step % 11UL);
|
Span<byte> modulusSpan = stackalloc byte[RemainderTableSize]
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
8,
|
||||||
|
5,
|
||||||
|
3,
|
||||||
|
7,
|
||||||
|
11,
|
||||||
|
};
|
||||||
|
|
||||||
ulong startDivisor64 = divisor128.Low;
|
ulong startDivisor64 = divisor128.Low;
|
||||||
byte remainder10 = (byte)(startDivisor64 % 10UL);
|
remainderSpan[0] = (byte)(startDivisor64 % 10UL);
|
||||||
byte remainder8 = (byte)(startDivisor64 % 8UL);
|
remainderSpan[1] = (byte)(startDivisor64 % 8UL);
|
||||||
byte remainder5 = (byte)(startDivisor64 % 5UL);
|
remainderSpan[2] = (byte)(startDivisor64 % 5UL);
|
||||||
byte remainder3 = (byte)(startDivisor64 % 3UL);
|
remainderSpan[3] = (byte)(startDivisor64 % 3UL);
|
||||||
byte remainder7 = (byte)(startDivisor64 % 7UL);
|
remainderSpan[4] = (byte)(startDivisor64 % 7UL);
|
||||||
byte remainder11 = (byte)(startDivisor64 % 11UL);
|
remainderSpan[5] = (byte)(startDivisor64 % 11UL);
|
||||||
|
|
||||||
|
stepSpan[0] = (byte)(step % 10UL);
|
||||||
|
stepSpan[1] = (byte)(step % 8UL);
|
||||||
|
stepSpan[2] = (byte)(step % 5UL);
|
||||||
|
stepSpan[3] = (byte)(step % 3UL);
|
||||||
|
stepSpan[4] = (byte)(step % 7UL);
|
||||||
|
stepSpan[5] = (byte)(step % 11UL);
|
||||||
|
|
||||||
bool lastIsSeven = (prime & 3UL) == 3UL;
|
bool lastIsSeven = (prime & 3UL) == 3UL;
|
||||||
|
|
||||||
|
MersenneNumberDivisorRemainderGpuStepper remainderStepper = new(stepSpan, modulusSpan);
|
||||||
|
MersenneNumberDivisorMontgomeryGpuBuilder? montgomeryBuilder = _montgomeryDevice == ByDivisorMontgomeryDevice.Gpu
|
||||||
|
? new MersenneNumberDivisorMontgomeryGpuBuilder(batchCapacity)
|
||||||
|
: null;
|
||||||
|
MontgomeryDivisorData[]? montgomeryBuffer = montgomeryBuilder != null
|
||||||
|
? ArrayPool<MontgomeryDivisorData>.Shared.Rent(batchCapacity)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
ulong remainingCandidates = ComputeRemainingCandidateCount(startDivisor64, limit64, step);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (divisor128.CompareTo(limit128) <= 0)
|
while (remainingCandidates > 0UL && divisor128.CompareTo(limit128) <= 0)
|
||||||
{
|
{
|
||||||
if (divisor128.High != 0UL)
|
if (divisor128.High != 0UL)
|
||||||
{
|
{
|
||||||
@@ -429,7 +466,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong currentDivisor = divisor128.Low;
|
ulong currentDivisor = divisor128.Low;
|
||||||
int chunkCount = ComputeChunkCount(currentDivisor, limit64, step, batchCapacity);
|
int chunkCount = remainingCandidates > (ulong)batchCapacity
|
||||||
|
? batchCapacity
|
||||||
|
: (int)remainingCandidates;
|
||||||
if (chunkCount <= 0)
|
if (chunkCount <= 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -442,22 +481,29 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
currentDivisor,
|
currentDivisor,
|
||||||
step,
|
step,
|
||||||
limit64,
|
limit64,
|
||||||
remainder10,
|
remainderSpan[0],
|
||||||
remainder8,
|
remainderSpan[1],
|
||||||
remainder5,
|
remainderSpan[2],
|
||||||
remainder3,
|
remainderSpan[3],
|
||||||
remainder7,
|
remainderSpan[4],
|
||||||
remainder11,
|
remainderSpan[5],
|
||||||
step10,
|
stepSpan[0],
|
||||||
step8,
|
stepSpan[1],
|
||||||
step5,
|
stepSpan[2],
|
||||||
step3,
|
stepSpan[3],
|
||||||
step7,
|
stepSpan[4],
|
||||||
step11,
|
stepSpan[5],
|
||||||
lastIsSeven,
|
lastIsSeven,
|
||||||
candidateSpan,
|
candidateSpan,
|
||||||
maskSpan);
|
maskSpan);
|
||||||
|
|
||||||
|
Span<MontgomeryDivisorData> montgomerySpan = default;
|
||||||
|
if (montgomeryBuilder != null && montgomeryBuffer != null)
|
||||||
|
{
|
||||||
|
montgomerySpan = montgomeryBuffer.AsSpan(0, chunkCount);
|
||||||
|
montgomeryBuilder.Build(candidateSpan, montgomerySpan);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < chunkCount; i++)
|
for (int i = 0; i < chunkCount; i++)
|
||||||
{
|
{
|
||||||
if (enforceLimit && (iterationCounter == 0UL || (iterationCounter & 63UL) == 0UL) && limitGuard.HasExpired())
|
if (enforceLimit && (iterationCounter == 0UL || (iterationCounter & 63UL) == 0UL) && limitGuard.HasExpired())
|
||||||
@@ -477,6 +523,17 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MontgomeryDivisorData divisorData;
|
||||||
|
if (!montgomerySpan.IsEmpty)
|
||||||
|
{
|
||||||
|
divisorData = montgomerySpan[i];
|
||||||
|
if (divisorData.Modulus != candidate)
|
||||||
|
{
|
||||||
|
divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (!hasCachedDivisorData || cachedModulus != candidate)
|
if (!hasCachedDivisorData || cachedModulus != candidate)
|
||||||
{
|
{
|
||||||
cachedDivisorData = MontgomeryDivisorData.FromModulus(candidate);
|
cachedDivisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||||
@@ -484,9 +541,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
hasCachedDivisorData = true;
|
hasCachedDivisorData = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = cachedDivisorData;
|
divisorData = cachedDivisorData;
|
||||||
ulong divisorCycle;
|
}
|
||||||
|
|
||||||
|
ulong divisorCycle;
|
||||||
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||||
{
|
{
|
||||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||||
@@ -516,67 +574,40 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
increment.Mul((ulong)chunkCount);
|
increment.Mul((ulong)chunkCount);
|
||||||
divisor128.Add(increment);
|
divisor128.Add(increment);
|
||||||
|
|
||||||
remainder10 = AdvanceRemainder(remainder10, step10, 10, chunkCount);
|
remainderStepper.Advance(chunkCount, remainderSpan);
|
||||||
remainder8 = AdvanceRemainder(remainder8, step8, 8, chunkCount);
|
remainingCandidates -= (ulong)chunkCount;
|
||||||
remainder5 = AdvanceRemainder(remainder5, step5, 5, chunkCount);
|
|
||||||
remainder3 = AdvanceRemainder(remainder3, step3, 3, chunkCount);
|
|
||||||
remainder7 = AdvanceRemainder(remainder7, step7, 7, chunkCount);
|
|
||||||
remainder11 = AdvanceRemainder(remainder11, step11, 11, chunkCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
montgomeryBuilder?.Dispose();
|
||||||
|
if (montgomeryBuffer != null)
|
||||||
|
{
|
||||||
|
ArrayPool<MontgomeryDivisorData>.Shared.Return(montgomeryBuffer, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
remainderStepper.Dispose();
|
||||||
|
ArrayPool<byte>.Shared.Return(remainderBuffer, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(stepBuffer, clearArray: false);
|
||||||
ArrayPool<ulong>.Shared.Return(candidateBuffer, clearArray: false);
|
ArrayPool<ulong>.Shared.Return(candidateBuffer, clearArray: false);
|
||||||
ArrayPool<byte>.Shared.Return(maskBuffer, clearArray: false);
|
ArrayPool<byte>.Shared.Return(maskBuffer, clearArray: false);
|
||||||
candidateEvaluator.Dispose();
|
candidateEvaluator.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
processedAll = divisor128.CompareTo(limit128) > 0;
|
processedAll = remainingCandidates == 0UL && divisor128.CompareTo(limit128) > 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ComputeChunkCount(ulong currentDivisor, ulong limit, ulong step, int maxCount)
|
private static ulong ComputeRemainingCandidateCount(ulong currentDivisor, ulong limit, ulong step)
|
||||||
{
|
{
|
||||||
if (maxCount <= 0 || step == 0UL || currentDivisor > limit)
|
if (step == 0UL || currentDivisor > limit)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong distance = limit - currentDivisor;
|
ulong distance = limit - currentDivisor;
|
||||||
ulong stepsAvailable = step == 0UL ? 0UL : distance / step;
|
ulong stepsAvailable = distance / step;
|
||||||
ulong total = stepsAvailable + 1UL;
|
return stepsAvailable + 1UL;
|
||||||
if (total > (ulong)maxCount)
|
|
||||||
{
|
|
||||||
total = (ulong)maxCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total > (ulong)int.MaxValue)
|
|
||||||
{
|
|
||||||
return int.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)total;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte AdvanceRemainder(byte remainder, byte step, byte modulus, int count)
|
|
||||||
{
|
|
||||||
if (modulus == 0 || count <= 0)
|
|
||||||
{
|
|
||||||
return remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
int modulusValue = modulus;
|
|
||||||
int remainderValue = remainder % modulusValue;
|
|
||||||
int stepValue = step % modulusValue;
|
|
||||||
|
|
||||||
long delta = (long)stepValue * count;
|
|
||||||
int adjusted = (int)((remainderValue + delta) % modulusValue);
|
|
||||||
if (adjusted < 0)
|
|
||||||
{
|
|
||||||
adjusted += modulusValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (byte)adjusted;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
|
using ILGPU;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable
|
||||||
|
{
|
||||||
|
private readonly GpuContextPool.GpuContextLease _lease;
|
||||||
|
private readonly Accelerator _accelerator;
|
||||||
|
private readonly Action<Index1D, ArrayView<ulong>, ArrayView<MontgomeryDivisorData>> _kernel;
|
||||||
|
private MemoryBuffer1D<ulong, Stride1D.Dense> _modulusBuffer;
|
||||||
|
private MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> _resultBuffer;
|
||||||
|
private ulong[] _hostModulusBuffer;
|
||||||
|
private MontgomeryDivisorData[] _hostResultBuffer;
|
||||||
|
private int _capacity;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
internal MersenneNumberDivisorMontgomeryGpuBuilder(int batchSize)
|
||||||
|
{
|
||||||
|
_lease = GpuContextPool.RentPreferred(preferCpu: false);
|
||||||
|
_accelerator = _lease.Accelerator;
|
||||||
|
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<MontgomeryDivisorData>>(BuildKernel);
|
||||||
|
_capacity = Math.Max(1, batchSize);
|
||||||
|
_modulusBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_resultBuffer = _accelerator.Allocate1D<MontgomeryDivisorData>(_capacity);
|
||||||
|
_hostModulusBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
|
_hostResultBuffer = ArrayPool<MontgomeryDivisorData>.Shared.Rent(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
_modulusBuffer.Dispose();
|
||||||
|
_resultBuffer.Dispose();
|
||||||
|
ArrayPool<ulong>.Shared.Return(_hostModulusBuffer, clearArray: false);
|
||||||
|
ArrayPool<MontgomeryDivisorData>.Shared.Return(_hostResultBuffer, clearArray: false);
|
||||||
|
_lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Build(ReadOnlySpan<ulong> moduli, Span<MontgomeryDivisorData> destination)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MersenneNumberDivisorMontgomeryGpuBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = moduli.Length;
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCapacity(length);
|
||||||
|
|
||||||
|
Span<ulong> hostModulusSpan = _hostModulusBuffer.AsSpan(0, length);
|
||||||
|
moduli.CopyTo(hostModulusSpan);
|
||||||
|
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> modulusView = _modulusBuffer.View.SubView(0, length);
|
||||||
|
modulusView.CopyFromCPU(ref MemoryMarshal.GetReference(hostModulusSpan), length);
|
||||||
|
|
||||||
|
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> resultView = _resultBuffer.View.SubView(0, length);
|
||||||
|
_kernel(length, modulusView, resultView);
|
||||||
|
|
||||||
|
Span<MontgomeryDivisorData> hostResultSpan = _hostResultBuffer.AsSpan(0, length);
|
||||||
|
resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostResultSpan), length);
|
||||||
|
|
||||||
|
hostResultSpan.CopyTo(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int required)
|
||||||
|
{
|
||||||
|
if (required <= _capacity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newCapacity = _capacity == 0 ? 1 : _capacity;
|
||||||
|
while (newCapacity < required)
|
||||||
|
{
|
||||||
|
newCapacity <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_modulusBuffer.Dispose();
|
||||||
|
_resultBuffer.Dispose();
|
||||||
|
ArrayPool<ulong>.Shared.Return(_hostModulusBuffer, clearArray: false);
|
||||||
|
ArrayPool<MontgomeryDivisorData>.Shared.Return(_hostResultBuffer, clearArray: false);
|
||||||
|
|
||||||
|
_capacity = newCapacity;
|
||||||
|
_modulusBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_resultBuffer = _accelerator.Allocate1D<MontgomeryDivisorData>(_capacity);
|
||||||
|
_hostModulusBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
|
_hostResultBuffer = ArrayPool<MontgomeryDivisorData>.Shared.Rent(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildKernel(Index1D index, ArrayView<ulong> moduli, ArrayView<MontgomeryDivisorData> destination)
|
||||||
|
{
|
||||||
|
int idx = index;
|
||||||
|
if (idx >= moduli.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong modulus = moduli[idx];
|
||||||
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
destination[idx] = default;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||||
|
ulong montgomeryOne = ComputeMontgomeryResidue64(modulus);
|
||||||
|
ulong montgomeryTwo = montgomeryOne.MulMod64GpuCompatible(2UL, modulus);
|
||||||
|
ulong montgomeryTwoSquared = MontgomeryMultiplyGpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||||
|
|
||||||
|
destination[idx] = new MontgomeryDivisorData(
|
||||||
|
modulus,
|
||||||
|
nPrime,
|
||||||
|
montgomeryOne,
|
||||||
|
montgomeryTwo,
|
||||||
|
montgomeryTwoSquared);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong ComputeMontgomeryResidue64(ulong modulus)
|
||||||
|
{
|
||||||
|
ulong remainder = ulong.MaxValue % modulus;
|
||||||
|
remainder++;
|
||||||
|
if (remainder >= modulus)
|
||||||
|
{
|
||||||
|
remainder -= modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return remainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong MontgomeryMultiplyGpu(ulong a, ulong b, ulong modulus, ulong nPrime)
|
||||||
|
{
|
||||||
|
GpuUInt128 product = new(a);
|
||||||
|
product.Mul64(new GpuUInt128(b));
|
||||||
|
ulong tLow = product.Low;
|
||||||
|
ulong tHigh = product.High;
|
||||||
|
|
||||||
|
ulong m = unchecked(tLow * nPrime);
|
||||||
|
GpuUInt128 mProduct = new(m);
|
||||||
|
mProduct.Mul64(new GpuUInt128(modulus));
|
||||||
|
ulong mLow = mProduct.Low;
|
||||||
|
ulong mHigh = mProduct.High;
|
||||||
|
|
||||||
|
ulong carry = unchecked((tLow + mLow) < tLow ? 1UL : 0UL);
|
||||||
|
ulong result = tHigh + mHigh + carry;
|
||||||
|
if (result >= modulus)
|
||||||
|
{
|
||||||
|
result -= modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||||||
|
{
|
||||||
|
ulong inv = modulus;
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
return unchecked(0UL - inv);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using ILGPU;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
|
||||||
|
{
|
||||||
|
private readonly GpuContextPool.GpuContextLease _lease;
|
||||||
|
private readonly Accelerator _accelerator;
|
||||||
|
private readonly Action<Index1D, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>> _kernel;
|
||||||
|
private MemoryBuffer1D<byte, Stride1D.Dense> _remainderBuffer;
|
||||||
|
private MemoryBuffer1D<byte, Stride1D.Dense> _stepBuffer;
|
||||||
|
private MemoryBuffer1D<byte, Stride1D.Dense> _modulusBuffer;
|
||||||
|
private MemoryBuffer1D<byte, Stride1D.Dense> _resultBuffer;
|
||||||
|
private byte[] _hostRemainders;
|
||||||
|
private byte[] _hostSteps;
|
||||||
|
private byte[] _hostModuli;
|
||||||
|
private readonly int _length;
|
||||||
|
private int _capacity;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
internal MersenneNumberDivisorRemainderGpuStepper(ReadOnlySpan<byte> steps, ReadOnlySpan<byte> moduli)
|
||||||
|
{
|
||||||
|
if (steps.Length != moduli.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Steps and moduli must have the same length.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_lease = GpuContextPool.RentPreferred(preferCpu: false);
|
||||||
|
_accelerator = _lease.Accelerator;
|
||||||
|
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>>(AdvanceRemaindersKernel);
|
||||||
|
|
||||||
|
_length = steps.Length;
|
||||||
|
_capacity = Math.Max(1, _length);
|
||||||
|
_remainderBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
_stepBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
_modulusBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
_resultBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
|
||||||
|
_hostRemainders = ArrayPool<byte>.Shared.Rent(_capacity);
|
||||||
|
_hostSteps = ArrayPool<byte>.Shared.Rent(_capacity);
|
||||||
|
_hostModuli = ArrayPool<byte>.Shared.Rent(_capacity);
|
||||||
|
|
||||||
|
steps.CopyTo(_hostSteps.AsSpan(0, _length));
|
||||||
|
moduli.CopyTo(_hostModuli.AsSpan(0, _length));
|
||||||
|
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> stepView = _stepBuffer.View.SubView(0, _length);
|
||||||
|
stepView.CopyFromCPU(ref MemoryMarshal.GetReference(_hostSteps.AsSpan(0, _length)), _length);
|
||||||
|
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> modulusView = _modulusBuffer.View.SubView(0, _length);
|
||||||
|
modulusView.CopyFromCPU(ref MemoryMarshal.GetReference(_hostModuli.AsSpan(0, _length)), _length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
_remainderBuffer.Dispose();
|
||||||
|
_stepBuffer.Dispose();
|
||||||
|
_modulusBuffer.Dispose();
|
||||||
|
_resultBuffer.Dispose();
|
||||||
|
ArrayPool<byte>.Shared.Return(_hostRemainders, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(_hostSteps, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(_hostModuli, clearArray: false);
|
||||||
|
_lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Advance(int advanceCount, Span<byte> remainders)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MersenneNumberDivisorRemainderGpuStepper));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (advanceCount <= 0 || remainders.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainders.Length != _length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Remainder buffer length must match the configured length.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> hostRemainderSpan = _hostRemainders.AsSpan(0, _length);
|
||||||
|
remainders.CopyTo(hostRemainderSpan);
|
||||||
|
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> remainderView = _remainderBuffer.View.SubView(0, _length);
|
||||||
|
remainderView.CopyFromCPU(ref MemoryMarshal.GetReference(hostRemainderSpan), _length);
|
||||||
|
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> stepView = _stepBuffer.View.SubView(0, _length);
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> modulusView = _modulusBuffer.View.SubView(0, _length);
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> resultView = _resultBuffer.View.SubView(0, _length);
|
||||||
|
|
||||||
|
_kernel(_length, advanceCount, remainderView, stepView, modulusView, resultView);
|
||||||
|
|
||||||
|
resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostRemainderSpan), _length);
|
||||||
|
hostRemainderSpan.CopyTo(remainders);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AdvanceRemaindersKernel(Index1D index, int advanceCount, ArrayView<byte> remainders, ArrayView<byte> steps, ArrayView<byte> moduli, ArrayView<byte> results)
|
||||||
|
{
|
||||||
|
int idx = index;
|
||||||
|
if (idx >= remainders.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int modulus = moduli[idx];
|
||||||
|
if (modulus == 0)
|
||||||
|
{
|
||||||
|
results[idx] = remainders[idx];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainder = remainders[idx] % modulus;
|
||||||
|
int step = steps[idx] % modulus;
|
||||||
|
|
||||||
|
long delta = (long)step * advanceCount;
|
||||||
|
int adjusted = (int)((remainder + delta) % modulus);
|
||||||
|
if (adjusted < 0)
|
||||||
|
{
|
||||||
|
adjusted += modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
results[idx] = (byte)adjusted;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user