Merge pull request #106 from mlemanczyk/codex/modify-evenperfectbitscanner-for-candidate-iteration
Finalize GPU remainder scan pipeline for by-divisor mode
This commit is contained in:
2 files changed
+343
-643
No files matched your search
@@ -22,6 +22,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>>> _kernelCache = new();
|
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>>> _kernelCache = new();
|
||||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>> _kernelExponentCache = new();
|
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>> _kernelExponentCache = new();
|
||||||
|
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte>> _remainderDeltaKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||||
|
private readonly ConcurrentDictionary<Accelerator, Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte>> _remainderScanKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||||
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
|
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
|
||||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
||||||
|
|
||||||
@@ -31,6 +33,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
private Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> GetKernelByPrimeExponent(Accelerator accelerator) =>
|
private Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> GetKernelByPrimeExponent(Accelerator accelerator) =>
|
||||||
_kernelExponentCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputeMontgomeryExponentKernel));
|
_kernelExponentCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputeMontgomeryExponentKernel));
|
||||||
|
|
||||||
|
private Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> GetRemainderDeltaKernel(Accelerator accelerator) =>
|
||||||
|
_remainderDeltaKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>, byte>(ComputeRemainderDeltasKernel));
|
||||||
|
|
||||||
|
private Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> GetRemainderScanKernel(Accelerator accelerator) =>
|
||||||
|
_remainderScanKernelCache.GetOrAdd(
|
||||||
|
accelerator,
|
||||||
|
acc => acc.LoadStreamKernel<Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte>(AccumulateRemaindersKernel));
|
||||||
|
|
||||||
public int GpuBatchSize
|
public int GpuBatchSize
|
||||||
{
|
{
|
||||||
get => _gpuBatchSize;
|
get => _gpuBatchSize;
|
||||||
@@ -94,6 +104,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
resources.DivisorsBuffer,
|
resources.DivisorsBuffer,
|
||||||
resources.ExponentBuffer,
|
resources.ExponentBuffer,
|
||||||
resources.HitsBuffer,
|
resources.HitsBuffer,
|
||||||
|
resources.DivisorDeltaBuffer,
|
||||||
|
resources.RemainderDeltaBuffer,
|
||||||
|
resources.RemainderBuffer,
|
||||||
|
resources.RemainderDeltaKernel,
|
||||||
|
resources.RemainderScanKernel,
|
||||||
resources.Divisors,
|
resources.Divisors,
|
||||||
resources.Exponents,
|
resources.Exponents,
|
||||||
resources.Hits,
|
resources.Hits,
|
||||||
@@ -152,6 +167,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
|
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
|
||||||
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer,
|
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer,
|
||||||
MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer,
|
MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer,
|
||||||
|
MemoryBuffer1D<ulong, Stride1D.Dense> divisorDeltaBuffer,
|
||||||
|
MemoryBuffer1D<byte, Stride1D.Dense> remainderDeltaBuffer,
|
||||||
|
MemoryBuffer1D<byte, Stride1D.Dense> remainderBuffer,
|
||||||
|
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> remainderDeltaKernel,
|
||||||
|
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> remainderScanKernel,
|
||||||
in ulong[] divisors,
|
in ulong[] divisors,
|
||||||
ulong[] exponents,
|
ulong[] exponents,
|
||||||
byte[] hits,
|
byte[] hits,
|
||||||
@@ -163,7 +183,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
int batchCapacity = (int)divisorsBuffer.Length;
|
int batchCapacity = (int)divisorsBuffer.Length;
|
||||||
bool composite = false;
|
bool composite = false;
|
||||||
bool processedAll = false;
|
bool processedAll = false;
|
||||||
ulong currentDivisor = 3UL;
|
|
||||||
processedCount = 0UL;
|
processedCount = 0UL;
|
||||||
lastProcessed = 0UL;
|
lastProcessed = 0UL;
|
||||||
|
|
||||||
@@ -177,55 +196,175 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||||
int cycleBatchCapacity = Math.Min(batchCapacity, cycleCache.PreferredBatchSize);
|
int cycleBatchCapacity = Math.Min(batchCapacity, cycleCache.PreferredBatchSize);
|
||||||
|
int maxKernelGroupSize = (int)accelerator.MaxNumThreadsPerGroup;
|
||||||
|
if (cycleBatchCapacity > maxKernelGroupSize)
|
||||||
|
{
|
||||||
|
cycleBatchCapacity = maxKernelGroupSize;
|
||||||
|
}
|
||||||
|
|
||||||
if (cycleBatchCapacity <= 0)
|
if (cycleBatchCapacity <= 0)
|
||||||
{
|
{
|
||||||
cycleBatchCapacity = batchCapacity;
|
cycleBatchCapacity = batchCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong[] cycleDivisors = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
ulong[] candidateDivisors = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||||
ulong[] cycleLengths = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||||
|
ulong[] filteredCycles = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||||
|
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||||
|
byte[] remainder10Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||||
|
byte[] remainder8Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||||
|
byte[] remainder5Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||||
|
byte[] remainder3Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||||
|
|
||||||
|
UInt128 twoP128 = (UInt128)prime << 1;
|
||||||
|
if (twoP128 == UInt128.Zero)
|
||||||
|
{
|
||||||
|
coveredRange = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 allowedMax128 = allowedMax;
|
||||||
|
UInt128 firstDivisor128 = twoP128 + UInt128.One;
|
||||||
|
if (firstDivisor128 > allowedMax128)
|
||||||
|
{
|
||||||
|
coveredRange = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UInt128 maxK128 = (allowedMax128 - UInt128.One) / twoP128;
|
||||||
|
if (maxK128 == UInt128.Zero)
|
||||||
|
{
|
||||||
|
coveredRange = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
|
||||||
|
ulong currentK = 1UL;
|
||||||
|
|
||||||
|
byte step10 = (byte)(twoP128 % 10UL);
|
||||||
|
byte step8 = (byte)(twoP128 % 8UL);
|
||||||
|
byte step5 = (byte)(twoP128 % 5UL);
|
||||||
|
byte step3 = (byte)(twoP128 % 3UL);
|
||||||
|
|
||||||
|
UInt128 currentDivisor128 = firstDivisor128;
|
||||||
|
byte remainder10 = (byte)((ulong)(currentDivisor128 % 10UL));
|
||||||
|
byte remainder8 = (byte)((ulong)(currentDivisor128 & 7UL));
|
||||||
|
byte remainder5 = (byte)((ulong)(currentDivisor128 % 5UL));
|
||||||
|
byte remainder3 = (byte)((ulong)(currentDivisor128 % 3UL));
|
||||||
|
bool lastIsSeven = (prime & 3UL) == 3UL;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (currentDivisor <= allowedMax)
|
while (currentK <= maxK)
|
||||||
{
|
{
|
||||||
int batchSize = 0;
|
int chunkCount = Math.Min(cycleBatchCapacity, batchCapacity);
|
||||||
bool reachedEndInBatch = false;
|
ulong remainingK = maxK - currentK + 1UL;
|
||||||
|
if ((ulong)chunkCount > remainingK)
|
||||||
while (batchSize < batchCapacity && currentDivisor <= allowedMax)
|
|
||||||
{
|
{
|
||||||
int chunkCount = Math.Min(cycleBatchCapacity, batchCapacity - batchSize);
|
chunkCount = (int)remainingK;
|
||||||
ulong remainingWidth = allowedMax - currentDivisor;
|
|
||||||
ulong maxOddCount = (remainingWidth >> 1) + 1UL;
|
|
||||||
if (maxOddCount < (ulong)chunkCount)
|
|
||||||
{
|
|
||||||
chunkCount = (int)maxOddCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkCount <= 0)
|
if (chunkCount <= 0)
|
||||||
{
|
{
|
||||||
reachedEndInBatch = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<ulong> chunkDivisors = cycleDivisors.AsSpan(0, chunkCount);
|
Span<ulong> candidateSpan = candidateDivisors.AsSpan(0, chunkCount);
|
||||||
Span<ulong> chunkCycles = cycleLengths.AsSpan(0, chunkCount);
|
Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount);
|
||||||
|
Span<byte> rem10Span = remainder10Buffer.AsSpan(0, chunkCount);
|
||||||
|
Span<byte> rem8Span = remainder8Buffer.AsSpan(0, chunkCount);
|
||||||
|
Span<byte> rem5Span = remainder5Buffer.AsSpan(0, chunkCount);
|
||||||
|
Span<byte> rem3Span = remainder3Buffer.AsSpan(0, chunkCount);
|
||||||
|
|
||||||
|
UInt128 localDivisor = currentDivisor128;
|
||||||
|
|
||||||
ulong fillDivisor = currentDivisor;
|
|
||||||
for (int i = 0; i < chunkCount; i++)
|
for (int i = 0; i < chunkCount; i++)
|
||||||
{
|
{
|
||||||
chunkDivisors[i] = fillDivisor;
|
ulong divisorValue = (ulong)localDivisor;
|
||||||
fillDivisor += 2UL;
|
candidateSpan[i] = divisorValue;
|
||||||
}
|
|
||||||
|
|
||||||
cycleCache.GetCycleLengths(chunkDivisors, chunkCycles);
|
|
||||||
|
|
||||||
for (int i = 0; i < chunkCount && batchSize < batchCapacity; i++)
|
|
||||||
{
|
|
||||||
ulong divisorValue = chunkDivisors[i];
|
|
||||||
ulong cycleLength = chunkCycles[i];
|
|
||||||
|
|
||||||
processedCount++;
|
processedCount++;
|
||||||
|
lastProcessed = divisorValue;
|
||||||
|
|
||||||
|
localDivisor += twoP128;
|
||||||
|
}
|
||||||
|
|
||||||
|
gapSpan[0] = 0UL;
|
||||||
|
for (int i = 1; i < chunkCount; i++)
|
||||||
|
{
|
||||||
|
gapSpan[i] = candidateSpan[i] - candidateSpan[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDivisor128 = localDivisor;
|
||||||
|
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> gapView = divisorDeltaBuffer.View.SubView(0, chunkCount);
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> deltaView = remainderDeltaBuffer.View.SubView(0, chunkCount);
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> remainderView = remainderBuffer.View.SubView(0, chunkCount);
|
||||||
|
|
||||||
|
gapView.CopyFromCPU(ref MemoryMarshal.GetReference(gapSpan), chunkCount);
|
||||||
|
|
||||||
|
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder10, 10, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem10Span);
|
||||||
|
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder8, 8, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem8Span);
|
||||||
|
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder5, 5, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem5Span);
|
||||||
|
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder3, 3, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem3Span);
|
||||||
|
|
||||||
|
remainder10 = AddMod(rem10Span[chunkCount - 1], step10, 10);
|
||||||
|
remainder8 = AddMod(rem8Span[chunkCount - 1], step8, 8);
|
||||||
|
remainder5 = AddMod(rem5Span[chunkCount - 1], step5, 5);
|
||||||
|
remainder3 = AddMod(rem3Span[chunkCount - 1], step3, 3);
|
||||||
|
|
||||||
|
int filteredCount = 0;
|
||||||
|
Span<ulong> filteredDivisorsSpan = filteredDivisors.AsSpan(0, chunkCount);
|
||||||
|
Span<ulong> filteredCyclesSpan = filteredCycles.AsSpan(0, chunkCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < chunkCount; i++)
|
||||||
|
{
|
||||||
|
byte r10 = rem10Span[i];
|
||||||
|
if (lastIsSeven)
|
||||||
|
{
|
||||||
|
if (r10 != 3 && r10 != 7 && r10 != 9)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (r10 != 1 && r10 != 3 && r10 != 7 && r10 != 9)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte r8 = rem8Span[i];
|
||||||
|
if (r8 != 1 && r8 != 7)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rem3Span[i] == 0 || rem5Span[i] == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredDivisorsSpan[filteredCount++] = candidateSpan[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
currentK += (ulong)chunkCount;
|
||||||
|
|
||||||
|
if (filteredCount == 0)
|
||||||
|
{
|
||||||
|
processedAll = currentK > maxK;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cycleCache.GetCycleLengths(filteredDivisorsSpan[..filteredCount], filteredCyclesSpan[..filteredCount]);
|
||||||
|
|
||||||
|
int batchSize = 0;
|
||||||
|
for (int i = 0; i < filteredCount; i++)
|
||||||
|
{
|
||||||
|
ulong divisorValue = filteredDivisorsSpan[i];
|
||||||
|
ulong cycleLength = filteredCyclesSpan[i];
|
||||||
|
|
||||||
bool includeDivisor = true;
|
bool includeDivisor = true;
|
||||||
ulong exponent = prime;
|
ulong exponent = prime;
|
||||||
|
|
||||||
@@ -247,32 +386,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeDivisor)
|
if (!includeDivisor)
|
||||||
{
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
divisors[batchSize] = divisorValue;
|
divisors[batchSize] = divisorValue;
|
||||||
exponents[batchSize] = exponent;
|
exponents[batchSize] = exponent;
|
||||||
batchSize++;
|
batchSize++;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastProcessed = divisorValue;
|
|
||||||
currentDivisor = divisorValue + 2UL;
|
|
||||||
|
|
||||||
if (currentDivisor > allowedMax)
|
|
||||||
{
|
|
||||||
reachedEndInBatch = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (batchSize == 0)
|
if (batchSize == 0)
|
||||||
{
|
{
|
||||||
if (reachedEndInBatch)
|
processedAll = currentK > maxK;
|
||||||
{
|
|
||||||
processedAll = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,22 +440,68 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reachedEndInBatch)
|
processedAll = currentK > maxK;
|
||||||
{
|
|
||||||
processedAll = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ArrayPool<ulong>.Shared.Return(cycleDivisors, clearArray: false);
|
ArrayPool<ulong>.Shared.Return(candidateDivisors, clearArray: false);
|
||||||
ArrayPool<ulong>.Shared.Return(cycleLengths, clearArray: false);
|
ArrayPool<ulong>.Shared.Return(filteredDivisors, clearArray: false);
|
||||||
|
ArrayPool<ulong>.Shared.Return(filteredCycles, clearArray: false);
|
||||||
|
ArrayPool<ulong>.Shared.Return(divisorGaps, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(remainder10Buffer, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(remainder8Buffer, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(remainder5Buffer, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(remainder3Buffer, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
coveredRange = composite || processedAll || currentDivisor > allowedMax;
|
coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128);
|
||||||
return composite;
|
return composite;
|
||||||
}
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void ComputeRemaindersOnGpu(
|
||||||
|
Accelerator accelerator,
|
||||||
|
int count,
|
||||||
|
byte baseRemainder,
|
||||||
|
byte modulus,
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> gapView,
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> deltaView,
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> remainderView,
|
||||||
|
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel,
|
||||||
|
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel,
|
||||||
|
Span<byte> destination)
|
||||||
|
{
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaKernel(count, gapView, deltaView, modulus);
|
||||||
|
SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic<int>(count);
|
||||||
|
Index1D gridDim = new Index1D(1);
|
||||||
|
Index1D groupDim = new Index1D(count);
|
||||||
|
KernelConfig kernelConfig = new KernelConfig(gridDim, groupDim, sharedMemoryConfig);
|
||||||
|
|
||||||
|
scanKernel(kernelConfig, new Index1D(count), deltaView, remainderView, baseRemainder, modulus);
|
||||||
|
accelerator.Synchronize();
|
||||||
|
remainderView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static byte AddMod(byte value, byte delta, byte modulus)
|
||||||
|
{
|
||||||
|
int result = value + delta;
|
||||||
|
if (result >= modulus)
|
||||||
|
{
|
||||||
|
result -= modulus;
|
||||||
|
if (result >= modulus)
|
||||||
|
{
|
||||||
|
result %= modulus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (byte)result;
|
||||||
|
}
|
||||||
|
|
||||||
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
@@ -581,6 +753,75 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ComputeRemainderDeltasKernel(Index1D index, ArrayView<ulong> gaps, ArrayView<byte> deltas, byte modulus)
|
||||||
|
{
|
||||||
|
deltas[index] = (byte)(gaps[index] % modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AccumulateRemaindersKernel(
|
||||||
|
Index1D index,
|
||||||
|
ArrayView<byte> deltas,
|
||||||
|
ArrayView<byte> remainders,
|
||||||
|
byte baseRemainder,
|
||||||
|
byte modulus)
|
||||||
|
{
|
||||||
|
int globalIndex = index;
|
||||||
|
int length = (int)remainders.Length;
|
||||||
|
if (globalIndex >= length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int localIndex = Group.IdxX;
|
||||||
|
int groupSize = Group.Dimension.X;
|
||||||
|
var shared = SharedMemory.GetDynamic<int>();
|
||||||
|
|
||||||
|
shared[localIndex] = deltas[globalIndex];
|
||||||
|
Group.Barrier();
|
||||||
|
|
||||||
|
int offset = 1;
|
||||||
|
int mod = modulus;
|
||||||
|
while (offset < groupSize)
|
||||||
|
{
|
||||||
|
int addend = 0;
|
||||||
|
if (localIndex >= offset)
|
||||||
|
{
|
||||||
|
addend = shared[localIndex - offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
Group.Barrier();
|
||||||
|
|
||||||
|
if (localIndex >= offset)
|
||||||
|
{
|
||||||
|
int sum = shared[localIndex] + addend;
|
||||||
|
if (sum >= mod)
|
||||||
|
{
|
||||||
|
sum %= mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared[localIndex] = sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
Group.Barrier();
|
||||||
|
|
||||||
|
offset <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalIndex == 0)
|
||||||
|
{
|
||||||
|
remainders[0] = baseRemainder;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainder = baseRemainder + shared[localIndex];
|
||||||
|
if (remainder >= mod)
|
||||||
|
{
|
||||||
|
remainder %= mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
remainders[globalIndex] = (byte)remainder;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ComputeMontgomeryExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
private static void ComputeMontgomeryExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
@@ -619,22 +860,27 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BatchResources(accelerator, capacity);
|
return new BatchResources(this, accelerator, capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReturnBatchResources(Accelerator accelerator, BatchResources resources) => _resourcePools.GetOrAdd(accelerator, static _ => []).Add(resources);
|
private void ReturnBatchResources(Accelerator accelerator, BatchResources resources) => _resourcePools.GetOrAdd(accelerator, static _ => []).Add(resources);
|
||||||
|
|
||||||
private sealed class BatchResources : IDisposable
|
private sealed class BatchResources : IDisposable
|
||||||
{
|
{
|
||||||
internal BatchResources(Accelerator accelerator, int capacity)
|
internal BatchResources(MersenneNumberDivisorByDivisorGpuTester owner, Accelerator accelerator, int capacity)
|
||||||
{
|
{
|
||||||
DivisorsBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(capacity);
|
DivisorsBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(capacity);
|
||||||
ExponentBuffer = accelerator.Allocate1D<ulong>(capacity);
|
ExponentBuffer = accelerator.Allocate1D<ulong>(capacity);
|
||||||
HitsBuffer = accelerator.Allocate1D<byte>(capacity);
|
HitsBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||||
|
DivisorDeltaBuffer = accelerator.Allocate1D<ulong>(capacity);
|
||||||
|
RemainderDeltaBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||||
|
RemainderBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||||
Divisors = ArrayPool<ulong>.Shared.Rent(capacity);
|
Divisors = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||||
Exponents = ArrayPool<ulong>.Shared.Rent(capacity);
|
Exponents = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||||
Hits = ArrayPool<byte>.Shared.Rent(capacity);
|
Hits = ArrayPool<byte>.Shared.Rent(capacity);
|
||||||
DivisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(capacity);
|
DivisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(capacity);
|
||||||
|
RemainderDeltaKernel = owner.GetRemainderDeltaKernel(accelerator);
|
||||||
|
RemainderScanKernel = owner.GetRemainderScanKernel(accelerator);
|
||||||
Capacity = capacity;
|
Capacity = capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,6 +890,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
internal readonly MemoryBuffer1D<byte, Stride1D.Dense> HitsBuffer;
|
internal readonly MemoryBuffer1D<byte, Stride1D.Dense> HitsBuffer;
|
||||||
|
|
||||||
|
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DivisorDeltaBuffer;
|
||||||
|
|
||||||
|
internal readonly MemoryBuffer1D<byte, Stride1D.Dense> RemainderDeltaBuffer;
|
||||||
|
|
||||||
|
internal readonly MemoryBuffer1D<byte, Stride1D.Dense> RemainderBuffer;
|
||||||
|
|
||||||
internal readonly ulong[] Divisors;
|
internal readonly ulong[] Divisors;
|
||||||
|
|
||||||
internal readonly ulong[] Exponents;
|
internal readonly ulong[] Exponents;
|
||||||
@@ -652,6 +904,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
internal readonly MontgomeryDivisorData[] DivisorData;
|
internal readonly MontgomeryDivisorData[] DivisorData;
|
||||||
|
|
||||||
|
internal readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> RemainderDeltaKernel;
|
||||||
|
|
||||||
|
internal readonly Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> RemainderScanKernel;
|
||||||
|
|
||||||
internal readonly int Capacity;
|
internal readonly int Capacity;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -659,6 +915,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
DivisorsBuffer.Dispose();
|
DivisorsBuffer.Dispose();
|
||||||
ExponentBuffer.Dispose();
|
ExponentBuffer.Dispose();
|
||||||
HitsBuffer.Dispose();
|
HitsBuffer.Dispose();
|
||||||
|
DivisorDeltaBuffer.Dispose();
|
||||||
|
RemainderDeltaBuffer.Dispose();
|
||||||
|
RemainderBuffer.Dispose();
|
||||||
ArrayPool<ulong>.Shared.Return(Divisors, clearArray: false);
|
ArrayPool<ulong>.Shared.Return(Divisors, clearArray: false);
|
||||||
ArrayPool<ulong>.Shared.Return(Exponents, clearArray: false);
|
ArrayPool<ulong>.Shared.Return(Exponents, clearArray: false);
|
||||||
ArrayPool<byte>.Shared.Return(Hits, clearArray: false);
|
ArrayPool<byte>.Shared.Return(Hits, clearArray: false);
|
||||||
|
|||||||
@@ -2,22 +2,12 @@ using System;
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Open.Numeric.Primes;
|
using Open.Numeric.Primes;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
public static class MersenneNumberDivisorByDivisorTester
|
public static class MersenneNumberDivisorByDivisorTester
|
||||||
{
|
{
|
||||||
private const int ByDivisorStateActive = 0;
|
|
||||||
private const int ByDivisorStateComposite = 1;
|
|
||||||
private const int ByDivisorStateCompleted = 2;
|
|
||||||
private const int ByDivisorStateCompletedDetailed = 3;
|
|
||||||
private const int DivisorAllocationBlockSize = 64;
|
|
||||||
|
|
||||||
public static void Run(
|
public static void Run(
|
||||||
List<ulong> candidates,
|
List<ulong> candidates,
|
||||||
IMersenneNumberDivisorByDivisorTester tester,
|
IMersenneNumberDivisorByDivisorTester tester,
|
||||||
@@ -34,14 +24,14 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = threadCount;
|
||||||
|
|
||||||
bool applyStartPrime = startPrime > 0UL;
|
bool applyStartPrime = startPrime > 0UL;
|
||||||
int skippedByPreviousResults = 0;
|
int skippedByPreviousResults = 0;
|
||||||
List<ByDivisorPrimeState> states = new(candidates.Count);
|
List<ulong> primesToTest = new(candidates.Count);
|
||||||
ulong maxPrime = 0UL;
|
ulong maxPrime = 0UL;
|
||||||
|
|
||||||
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
|
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
|
||||||
// TODO: Swap this per-run enumerator creation for the shared PrimeIterator so by-divisor scans reuse the
|
|
||||||
// benchmarked zero-allocation prime sequence instead of instantiating a new iterator for every invocation.
|
|
||||||
bool hasPrime = primeEnumerator.MoveNext();
|
bool hasPrime = primeEnumerator.MoveNext();
|
||||||
ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
|
ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
|
||||||
|
|
||||||
@@ -90,10 +80,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
maxPrime = candidate;
|
maxPrime = candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
states.Add(new ByDivisorPrimeState
|
primesToTest.Add(candidate);
|
||||||
{
|
|
||||||
Prime = candidate,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skippedByPreviousResults > 0)
|
if (skippedByPreviousResults > 0)
|
||||||
@@ -101,7 +88,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
|
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states.Count == 0)
|
if (primesToTest.Count == 0)
|
||||||
{
|
{
|
||||||
if (applyStartPrime)
|
if (applyStartPrime)
|
||||||
{
|
{
|
||||||
@@ -123,27 +110,28 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
|
|
||||||
tester.ConfigureFromMaxPrime(maxPrime);
|
tester.ConfigureFromMaxPrime(maxPrime);
|
||||||
|
|
||||||
int stateCount = states.Count;
|
int primeCount = primesToTest.Count;
|
||||||
ulong[] primeBatch = ArrayPool<ulong>.Shared.Rent(stateCount);
|
ulong[] primeBatch = ArrayPool<ulong>.Shared.Rent(primeCount);
|
||||||
ulong[] allowedMaxBatch = ArrayPool<ulong>.Shared.Rent(stateCount);
|
ulong[] allowedMaxBatch = ArrayPool<ulong>.Shared.Rent(primeCount);
|
||||||
|
List<ulong> filteredPrimes = new(primeCount);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Span<ulong> primeBatchSpan = primeBatch.AsSpan(0, stateCount);
|
Span<ulong> primeSpan = primeBatch.AsSpan(0, primeCount);
|
||||||
Span<ulong> allowedMaxSpan = allowedMaxBatch.AsSpan(0, stateCount);
|
Span<ulong> allowedMaxSpan = allowedMaxBatch.AsSpan(0, primeCount);
|
||||||
|
|
||||||
for (int batchIndex = 0; batchIndex < stateCount; batchIndex++)
|
for (int i = 0; i < primeCount; i++)
|
||||||
{
|
{
|
||||||
primeBatchSpan[batchIndex] = states[batchIndex].Prime;
|
primeSpan[i] = primesToTest[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
tester.PrepareCandidates(primeBatchSpan, allowedMaxSpan);
|
tester.PrepareCandidates(primeSpan, allowedMaxSpan);
|
||||||
|
|
||||||
List<ByDivisorPrimeState> filteredStates = new(stateCount);
|
for (int i = 0; i < primeCount; i++)
|
||||||
for (int stateIndex = 0; stateIndex < stateCount; stateIndex++)
|
|
||||||
{
|
{
|
||||||
ulong prime = primeBatchSpan[stateIndex];
|
ulong prime = primeSpan[i];
|
||||||
ulong allowedMax = allowedMaxSpan[stateIndex];
|
ulong allowedMax = allowedMaxSpan[i];
|
||||||
|
|
||||||
if (allowedMax < 3UL)
|
if (allowedMax < 3UL)
|
||||||
{
|
{
|
||||||
clearComposite();
|
clearComposite();
|
||||||
@@ -151,17 +139,8 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredStates.Add(new ByDivisorPrimeState
|
filteredPrimes.Add(prime);
|
||||||
{
|
|
||||||
Prime = prime,
|
|
||||||
AllowedMax = allowedMax,
|
|
||||||
Completed = false,
|
|
||||||
Composite = false,
|
|
||||||
DetailedCheck = false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
states = filteredStates;
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -169,7 +148,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
ArrayPool<ulong>.Shared.Return(primeBatch, clearArray: true);
|
ArrayPool<ulong>.Shared.Return(primeBatch, clearArray: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states.Count == 0)
|
if (filteredPrimes.Count == 0)
|
||||||
{
|
{
|
||||||
if (applyStartPrime)
|
if (applyStartPrime)
|
||||||
{
|
{
|
||||||
@@ -179,557 +158,19 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stateCount = states.Count;
|
foreach (ulong prime in filteredPrimes)
|
||||||
ulong[] primeValues = new ulong[stateCount];
|
{
|
||||||
ulong[] allowedMaxValues = new ulong[stateCount];
|
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
|
||||||
int[] stateFlags = new int[stateCount];
|
|
||||||
|
|
||||||
for (int stateIndex = 0; stateIndex < stateCount; stateIndex++)
|
if (!isPrime)
|
||||||
{
|
|
||||||
ByDivisorPrimeState currentState = states[stateIndex];
|
|
||||||
primeValues[stateIndex] = currentState.Prime;
|
|
||||||
allowedMaxValues[stateIndex] = currentState.AllowedMax;
|
|
||||||
stateFlags[stateIndex] = ByDivisorStateActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
states.Clear();
|
|
||||||
|
|
||||||
ulong divisorLimit = tester.DivisorLimit;
|
|
||||||
ulong nextDivisor = 3UL;
|
|
||||||
long finalDivisorBits = unchecked((long)3UL);
|
|
||||||
int divisorsExhaustedFlag = 0;
|
|
||||||
int finalizerState = 0;
|
|
||||||
int finalizationCompleted = 0;
|
|
||||||
int remainingStates = stateCount;
|
|
||||||
int activeStartIndex = 0;
|
|
||||||
long[] activeStateMask = new long[(stateCount + 63) >> 6];
|
|
||||||
|
|
||||||
for (int maskStateIndex = 0; maskStateIndex < stateCount; maskStateIndex++)
|
|
||||||
{
|
|
||||||
int wordIndex = maskStateIndex >> 6;
|
|
||||||
int bitIndex = maskStateIndex & 63;
|
|
||||||
activeStateMask[wordIndex] |= 1L << bitIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task[] workers = new Task[Math.Max(1, threadCount)];
|
|
||||||
|
|
||||||
for (int workerIndex = 0; workerIndex < workers.Length; workerIndex++)
|
|
||||||
{
|
|
||||||
int capturedStateCount = stateCount;
|
|
||||||
// TODO: Swap Task.Factory.StartNew for the shared low-overhead work queue once the scanner's
|
|
||||||
// scheduler is exposed so divisor scans avoid the extra allocations and context switches that
|
|
||||||
// benchmarks showed with Task-based fan-out.
|
|
||||||
workers[workerIndex] = Task.Factory.StartNew(() =>
|
|
||||||
{
|
|
||||||
using var session = tester.CreateDivisorSession();
|
|
||||||
byte[] hitsBuffer = ArrayPool<byte>.Shared.Rent(capturedStateCount);
|
|
||||||
ulong[] primeBuffer = ArrayPool<ulong>.Shared.Rent(capturedStateCount);
|
|
||||||
int[] indexBuffer = ArrayPool<int>.Shared.Rent(capturedStateCount);
|
|
||||||
PendingResult[] completionsBuffer = ArrayPool<PendingResult>.Shared.Rent(capturedStateCount);
|
|
||||||
PendingResult[] compositesBuffer = ArrayPool<PendingResult>.Shared.Rent(capturedStateCount);
|
|
||||||
int completionsCount = 0;
|
|
||||||
int compositesCount = 0;
|
|
||||||
ulong localDivisorCursor = 0UL;
|
|
||||||
int localDivisorsRemaining = 0;
|
|
||||||
bool exhausted = false;
|
|
||||||
ulong divisor;
|
|
||||||
int activeCount;
|
|
||||||
Span<byte> hitsSpan = default;
|
|
||||||
int hitIndex = 0;
|
|
||||||
int index;
|
|
||||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
|
|
||||||
ulong divisorCycle = 0UL;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (Volatile.Read(ref remainingStates) == 0)
|
|
||||||
{
|
|
||||||
if (Volatile.Read(ref finalizationCompleted) != 0 || Volatile.Read(ref divisorsExhaustedFlag) == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
divisor = AcquireNextDivisor(ref nextDivisor, divisorLimit, ref divisorsExhaustedFlag, ref finalDivisorBits, out exhausted, ref localDivisorCursor, ref localDivisorsRemaining);
|
|
||||||
if (divisor != 0UL)
|
|
||||||
{
|
|
||||||
|
|
||||||
activeCount = BuildPrimeBuffer(
|
|
||||||
divisor,
|
|
||||||
primeValues,
|
|
||||||
allowedMaxValues,
|
|
||||||
stateFlags,
|
|
||||||
primeBuffer,
|
|
||||||
indexBuffer,
|
|
||||||
completionsBuffer,
|
|
||||||
ref completionsCount,
|
|
||||||
ref remainingStates,
|
|
||||||
activeStateMask,
|
|
||||||
ref activeStartIndex,
|
|
||||||
markComposite,
|
|
||||||
clearComposite,
|
|
||||||
printResult);
|
|
||||||
|
|
||||||
if (completionsCount > 0)
|
|
||||||
{
|
|
||||||
FlushPendingResults(completionsBuffer, ref completionsCount, markComposite, clearComposite, printResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeCount == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
divisorCycle = 0UL;
|
|
||||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
|
||||||
{
|
|
||||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(16);
|
|
||||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(divisor, primeBuffer[0], factorCache, out divisorCycle) || divisorCycle == 0UL)
|
|
||||||
{
|
|
||||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hitsSpan = hitsBuffer.AsSpan(0, activeCount);
|
|
||||||
hitsSpan.Clear();
|
|
||||||
session.CheckDivisor(divisor, divisorCycle, primeBuffer.AsSpan(0, activeCount), hitsSpan);
|
|
||||||
|
|
||||||
for (hitIndex = 0; hitIndex < activeCount; hitIndex++)
|
|
||||||
{
|
|
||||||
if (hitsSpan[hitIndex] == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = indexBuffer[hitIndex];
|
|
||||||
if (Interlocked.CompareExchange(ref stateFlags[index], ByDivisorStateComposite, ByDivisorStateActive) == ByDivisorStateActive)
|
|
||||||
{
|
|
||||||
ClearActiveMask(activeStateMask, index);
|
|
||||||
Interlocked.Decrement(ref remainingStates);
|
|
||||||
compositesBuffer[compositesCount++] = new PendingResult(primeValues[index], detailedCheck: true, passedAllTests: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compositesCount == compositesBuffer.Length)
|
|
||||||
{
|
|
||||||
FlushPendingResults(compositesBuffer, ref compositesCount, markComposite, clearComposite, printResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compositesCount > 0)
|
|
||||||
{
|
|
||||||
FlushPendingResults(compositesBuffer, ref compositesCount, markComposite, clearComposite, printResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!exhausted)
|
|
||||||
{
|
|
||||||
if (Volatile.Read(ref remainingStates) == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.Yield();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Interlocked.CompareExchange(ref finalizerState, 1, 0) == 0)
|
|
||||||
{
|
|
||||||
FinalizeRemainingStates(
|
|
||||||
primeValues,
|
|
||||||
allowedMaxValues,
|
|
||||||
stateFlags,
|
|
||||||
ref remainingStates,
|
|
||||||
ref finalDivisorBits,
|
|
||||||
completionsBuffer,
|
|
||||||
ref completionsCount,
|
|
||||||
activeStateMask,
|
|
||||||
clearComposite,
|
|
||||||
printResult,
|
|
||||||
markComposite);
|
|
||||||
|
|
||||||
if (completionsCount > 0)
|
|
||||||
{
|
|
||||||
FlushPendingResults(completionsBuffer, ref completionsCount, markComposite, clearComposite, printResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
Volatile.Write(ref finalizationCompleted, 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (Volatile.Read(ref finalizationCompleted) == 0 && Volatile.Read(ref remainingStates) > 0)
|
|
||||||
{
|
|
||||||
Thread.Yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Volatile.Read(ref remainingStates) == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (completionsCount > 0)
|
|
||||||
{
|
|
||||||
FlushPendingResults(completionsBuffer, ref completionsCount, markComposite, clearComposite, printResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compositesCount > 0)
|
|
||||||
{
|
|
||||||
FlushPendingResults(compositesBuffer, ref compositesCount, markComposite, clearComposite, printResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayPool<PendingResult>.Shared.Return(compositesBuffer, clearArray: true);
|
|
||||||
ArrayPool<PendingResult>.Shared.Return(completionsBuffer, clearArray: true);
|
|
||||||
ArrayPool<int>.Shared.Return(indexBuffer, clearArray: true);
|
|
||||||
ArrayPool<ulong>.Shared.Return(primeBuffer, clearArray: true);
|
|
||||||
ArrayPool<byte>.Shared.Return(hitsBuffer, clearArray: true);
|
|
||||||
}
|
|
||||||
}, TaskCreationOptions.LongRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.WaitAll(workers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong AcquireNextDivisor(
|
|
||||||
ref ulong nextDivisor,
|
|
||||||
ulong divisorLimit,
|
|
||||||
ref int divisorsExhaustedFlag,
|
|
||||||
ref long finalDivisorBits,
|
|
||||||
out bool exhausted,
|
|
||||||
ref ulong localDivisorCursor,
|
|
||||||
ref int localDivisorsRemaining)
|
|
||||||
{
|
|
||||||
ref long nextDivisorBits = ref Unsafe.As<ulong, long>(ref nextDivisor);
|
|
||||||
ulong blockStride = unchecked(DivisorAllocationBlockSize * 2UL);
|
|
||||||
|
|
||||||
// TODO: Replace this odd-only linear allocator with the divisor-cycle aware stride planner once the cache exposes
|
|
||||||
// skip tables so we can jump directly to viable divisors instead of allocating every odd candidate.
|
|
||||||
if (TryAcquireLocalDivisor(ref localDivisorCursor, ref localDivisorsRemaining, out ulong localDivisor))
|
|
||||||
{
|
|
||||||
exhausted = false;
|
|
||||||
return localDivisor;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (Volatile.Read(ref divisorsExhaustedFlag) != 0)
|
|
||||||
{
|
|
||||||
exhausted = true;
|
|
||||||
return 0UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
long currentBits = Volatile.Read(ref nextDivisorBits);
|
|
||||||
ulong currentValue = unchecked((ulong)currentBits);
|
|
||||||
if (currentValue > divisorLimit)
|
|
||||||
{
|
|
||||||
if (Interlocked.CompareExchange(ref divisorsExhaustedFlag, 1, 0) == 0)
|
|
||||||
{
|
|
||||||
Volatile.Write(ref finalDivisorBits, currentBits);
|
|
||||||
}
|
|
||||||
|
|
||||||
exhausted = true;
|
|
||||||
return 0UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong maximumNext = divisorLimit >= ulong.MaxValue - 1UL ? ulong.MaxValue : divisorLimit + 2UL;
|
|
||||||
ulong requestedNext = currentValue > ulong.MaxValue - blockStride ? ulong.MaxValue : currentValue + blockStride;
|
|
||||||
if (requestedNext > maximumNext)
|
|
||||||
{
|
|
||||||
requestedNext = maximumNext;
|
|
||||||
}
|
|
||||||
|
|
||||||
long nextBits = unchecked((long)requestedNext);
|
|
||||||
if (Interlocked.CompareExchange(ref nextDivisorBits, nextBits, currentBits) != currentBits)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong available = requestedNext - currentValue;
|
|
||||||
if (available == 0UL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = (int)(available >> 1);
|
|
||||||
if (count <= 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
localDivisorCursor = currentValue;
|
|
||||||
localDivisorsRemaining = count;
|
|
||||||
|
|
||||||
if (TryAcquireLocalDivisor(ref localDivisorCursor, ref localDivisorsRemaining, out ulong candidate))
|
|
||||||
{
|
|
||||||
exhausted = false;
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
localDivisorCursor = 0UL;
|
|
||||||
localDivisorsRemaining = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static bool TryAcquireLocalDivisor(ref ulong cursor, ref int remaining, out ulong divisor)
|
|
||||||
{
|
|
||||||
while (remaining > 0)
|
|
||||||
{
|
|
||||||
ulong candidate = cursor;
|
|
||||||
cursor += 2UL;
|
|
||||||
remaining--;
|
|
||||||
|
|
||||||
// TODO: Integrate divisor-cycle skip tables here so the local allocator leaps across cached composite
|
|
||||||
// spans instead of probing every odd candidate sequentially.
|
|
||||||
if (IsAllowedDivisorCandidate(candidate))
|
|
||||||
{
|
|
||||||
divisor = candidate;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
divisor = 0UL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static bool IsAllowedDivisorCandidate(ulong divisor)
|
|
||||||
{
|
|
||||||
if ((divisor & 1UL) == 0UL)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((divisor % 3UL) == 0UL || (divisor % 5UL) == 0UL || (divisor % 7UL) == 0UL || (divisor % 11UL) == 0UL)
|
|
||||||
{
|
|
||||||
// TODO: Replace these `%` filters with Mod3/Mod5/Mod7/Mod11 helpers so candidate sieving reuses the
|
|
||||||
// benchmarked lookup-based residues instead of executing slow modulo instructions for every divisor.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int BuildPrimeBuffer(
|
|
||||||
ulong divisor,
|
|
||||||
in ulong[] primeValues,
|
|
||||||
in ulong[] allowedMaxValues,
|
|
||||||
int[] stateFlags,
|
|
||||||
ulong[] primeBuffer,
|
|
||||||
int[] indexBuffer,
|
|
||||||
PendingResult[] completionsBuffer,
|
|
||||||
ref int completionsCount,
|
|
||||||
ref int remainingStates,
|
|
||||||
long[] activeStateMask,
|
|
||||||
ref int activeStartIndex,
|
|
||||||
Action markComposite,
|
|
||||||
Action clearComposite,
|
|
||||||
Action<ulong, bool, bool, bool> printResult)
|
|
||||||
{
|
|
||||||
int length = primeValues.Length;
|
|
||||||
int startIndex;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
startIndex = Volatile.Read(ref activeStartIndex);
|
|
||||||
if (startIndex >= length || allowedMaxValues[startIndex] >= divisor)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Interlocked.CompareExchange(ref activeStartIndex, startIndex + 1, startIndex) != startIndex)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int state = Volatile.Read(ref stateFlags[startIndex]);
|
|
||||||
if (state == ByDivisorStateActive)
|
|
||||||
{
|
|
||||||
ClearActiveMask(activeStateMask, startIndex);
|
|
||||||
Interlocked.Decrement(ref remainingStates);
|
|
||||||
if (completionsCount == completionsBuffer.Length)
|
|
||||||
{
|
|
||||||
FlushPendingResults(completionsBuffer, ref completionsCount, markComposite, clearComposite, printResult);
|
|
||||||
}
|
|
||||||
completionsBuffer[completionsCount++] = new PendingResult(primeValues[startIndex], detailedCheck: true, passedAllTests: true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ClearActiveMask(activeStateMask, startIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = startIndex;
|
|
||||||
int activeCount = 0;
|
|
||||||
while (index < length)
|
|
||||||
{
|
|
||||||
int wordIndex = index >> 6;
|
|
||||||
ulong word = unchecked((ulong)activeStateMask[wordIndex]);
|
|
||||||
if (word == 0UL)
|
|
||||||
{
|
|
||||||
word = unchecked((ulong)Volatile.Read(ref activeStateMask[wordIndex]));
|
|
||||||
if (word == 0UL)
|
|
||||||
{
|
|
||||||
index = (wordIndex + 1) << 6;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int bitOffset = index & 63;
|
|
||||||
if (bitOffset != 0)
|
|
||||||
{
|
|
||||||
word &= ulong.MaxValue << bitOffset;
|
|
||||||
if (word == 0UL)
|
|
||||||
{
|
|
||||||
index = (wordIndex + 1) << 6;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (word != 0UL)
|
|
||||||
{
|
|
||||||
int candidateIndex = (wordIndex << 6) + BitOperations.TrailingZeroCount(word);
|
|
||||||
if (candidateIndex >= length)
|
|
||||||
{
|
|
||||||
return activeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stateFlags[candidateIndex] == ByDivisorStateActive)
|
|
||||||
{
|
|
||||||
primeBuffer[activeCount] = primeValues[candidateIndex];
|
|
||||||
indexBuffer[activeCount] = candidateIndex;
|
|
||||||
activeCount++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ClearActiveMask(activeStateMask, candidateIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
word &= word - 1UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = (wordIndex + 1) << 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
return activeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void FinalizeRemainingStates(
|
|
||||||
ulong[] primeValues,
|
|
||||||
ulong[] allowedMaxValues,
|
|
||||||
int[] stateFlags,
|
|
||||||
ref int remainingStates,
|
|
||||||
ref long finalDivisorBits,
|
|
||||||
PendingResult[] completionsBuffer,
|
|
||||||
ref int completionsCount,
|
|
||||||
long[] activeStateMask,
|
|
||||||
Action clearComposite,
|
|
||||||
Action<ulong, bool, bool, bool> printResult,
|
|
||||||
Action markComposite)
|
|
||||||
{
|
|
||||||
for (int finalizeIndex = 0; finalizeIndex < primeValues.Length; finalizeIndex++)
|
|
||||||
{
|
|
||||||
if (stateFlags[finalizeIndex] != ByDivisorStateActive)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong finalDivisor = unchecked((ulong)Volatile.Read(ref finalDivisorBits));
|
|
||||||
bool detailed = finalDivisor > allowedMaxValues[finalizeIndex];
|
|
||||||
|
|
||||||
if (Interlocked.CompareExchange(ref stateFlags[finalizeIndex], detailed ? ByDivisorStateCompletedDetailed : ByDivisorStateCompleted, ByDivisorStateActive) != ByDivisorStateActive)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearActiveMask(activeStateMask, finalizeIndex);
|
|
||||||
Interlocked.Decrement(ref remainingStates);
|
|
||||||
if (completionsCount == completionsBuffer.Length)
|
|
||||||
{
|
|
||||||
FlushPendingResults(completionsBuffer, ref completionsCount, markComposite, clearComposite, printResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
completionsBuffer[completionsCount++] = new PendingResult(primeValues[finalizeIndex], detailedCheck: detailed, passedAllTests: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void FlushPendingResults(
|
|
||||||
PendingResult[] buffer,
|
|
||||||
ref int count,
|
|
||||||
Action markComposite,
|
|
||||||
Action clearComposite,
|
|
||||||
Action<ulong, bool, bool, bool> printResult)
|
|
||||||
{
|
|
||||||
for (int flushIndex = 0; flushIndex < count; flushIndex++)
|
|
||||||
{
|
|
||||||
PendingResult result = buffer[flushIndex];
|
|
||||||
if (result.PassedAllTests)
|
|
||||||
{
|
|
||||||
clearComposite();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
markComposite();
|
markComposite();
|
||||||
|
printResult(prime, true, true, false);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
printResult(result.Prime, true, result.DetailedCheck, result.PassedAllTests);
|
clearComposite();
|
||||||
|
printResult(prime, true, divisorsExhausted, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ClearActiveMask(long[] activeStateMask, int index)
|
|
||||||
{
|
|
||||||
int wordIndex = index >> 6;
|
|
||||||
long bit = 1L << (index & 63);
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
long current = Volatile.Read(ref activeStateMask[wordIndex]);
|
|
||||||
if ((current & bit) == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Interlocked.CompareExchange(ref activeStateMask[wordIndex], current & ~bit, current) == current)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct ByDivisorPrimeState
|
|
||||||
{
|
|
||||||
internal ulong Prime;
|
|
||||||
internal ulong AllowedMax;
|
|
||||||
internal bool Completed;
|
|
||||||
internal bool Composite;
|
|
||||||
internal bool DetailedCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct PendingResult
|
|
||||||
{
|
|
||||||
internal PendingResult(ulong prime, bool detailedCheck, bool passedAllTests)
|
|
||||||
{
|
|
||||||
Prime = prime;
|
|
||||||
DetailedCheck = detailedCheck;
|
|
||||||
PassedAllTests = passedAllTests;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal ulong Prime { get; }
|
|
||||||
|
|
||||||
internal bool DetailedCheck { get; }
|
|
||||||
|
|
||||||
internal bool PassedAllTests { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user