Restore cycle-aware divisor scans and background generation
This commit is contained in:
1 parent
bc4e526946
commit
b798970ce9
6 files changed
+245
-491
No files matched your search
@@ -56,7 +56,9 @@ Never ever run tests marked with trait Category = Slow. Only run tests with cate
|
|||||||
|
|
||||||
There is only one agents file - this one. Don't search for another one.
|
There is only one agents file - this one. Don't search for another one.
|
||||||
|
|
||||||
Always use indentation with 4 spaces and / or 1 tab in source code files.
|
Always use indentation with 4 spaces in source code files. Do not use 8-space indentation or tabs.
|
||||||
|
|
||||||
|
Keep the divisor cycle cache limited to three blocks in memory (the base snapshot plus at most two additional blocks) and always start background computation of the next block immediately after loading the first block from disk and whenever work begins on the most recently generated block.
|
||||||
|
|
||||||
All code, comments, commit messages, branch names and PR descriptions must be written in American English.
|
All code, comments, commit messages, branch names and PR descriptions must be written in American English.
|
||||||
|
|
||||||
|
|||||||
@@ -457,6 +457,7 @@ internal static class Program
|
|||||||
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
|
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DivisorCycleCache.Shared.ConfigureGeneratorDevice(orderOnGpu);
|
||||||
DivisorCycleCache.Shared.ReloadFromCurrentSnapshot();
|
DivisorCycleCache.Shared.ReloadFromCurrentSnapshot();
|
||||||
|
|
||||||
Console.WriteLine("Divisor cycles are ready");
|
Console.WriteLine("Divisor cycles are ready");
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
@@ -8,7 +7,6 @@ using MontgomeryDivisorData = PerfectNumbers.Core.Gpu.MersenneNumberDivisorByDiv
|
|||||||
|
|
||||||
namespace PerfectNumbers.Core.Cpu;
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
// TODO: The current implementation is over-complicated for CPU. We shouldn't need all the hassle with intermediate buffers, checking the status there etc.
|
|
||||||
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
||||||
{
|
{
|
||||||
private readonly object _sync = new();
|
private readonly object _sync = new();
|
||||||
@@ -74,7 +72,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
{
|
{
|
||||||
ulong allowedMax;
|
ulong allowedMax;
|
||||||
bool useCycles;
|
bool useCycles;
|
||||||
int batchCapacity;
|
|
||||||
|
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
@@ -85,7 +82,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
useCycles = _useDivisorCycles;
|
useCycles = _useDivisorCycles;
|
||||||
batchCapacity = _batchSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowedMax < 3UL)
|
if (allowedMax < 3UL)
|
||||||
@@ -94,34 +90,17 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong[] divisors = ArrayPool<ulong>.Shared.Rent(batchCapacity);
|
|
||||||
byte[] hits = ArrayPool<byte>.Shared.Rent(batchCapacity);
|
|
||||||
MontgomeryDivisorData[] divisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(batchCapacity);
|
|
||||||
|
|
||||||
ulong processedCount = 0UL;
|
ulong processedCount = 0UL;
|
||||||
ulong lastProcessed = 0UL;
|
ulong lastProcessed = 0UL;
|
||||||
bool composite = false;
|
|
||||||
bool processedAll = false;
|
bool processedAll = false;
|
||||||
|
|
||||||
try
|
bool composite = CheckDivisors(
|
||||||
{
|
|
||||||
composite = CheckDivisors(
|
|
||||||
prime,
|
prime,
|
||||||
allowedMax,
|
allowedMax,
|
||||||
useCycles,
|
useCycles,
|
||||||
divisors,
|
|
||||||
hits,
|
|
||||||
divisorData,
|
|
||||||
out lastProcessed,
|
out lastProcessed,
|
||||||
out processedAll,
|
out processedAll,
|
||||||
out processedCount);
|
out processedCount);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<ulong>.Shared.Return(divisors, clearArray: false);
|
|
||||||
ArrayPool<byte>.Shared.Return(hits, clearArray: false);
|
|
||||||
ArrayPool<MontgomeryDivisorData>.Shared.Return(divisorData, clearArray: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (processedCount > 0UL)
|
if (processedCount > 0UL)
|
||||||
{
|
{
|
||||||
@@ -169,9 +148,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
ulong prime,
|
ulong prime,
|
||||||
ulong allowedMax,
|
ulong allowedMax,
|
||||||
bool useCycles,
|
bool useCycles,
|
||||||
ulong[] divisors,
|
|
||||||
byte[] hits,
|
|
||||||
MontgomeryDivisorData[] divisorData,
|
|
||||||
out ulong lastProcessed,
|
out ulong lastProcessed,
|
||||||
out bool processedAll,
|
out bool processedAll,
|
||||||
out ulong processedCount)
|
out ulong processedCount)
|
||||||
@@ -185,92 +161,53 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int batchCapacity = _batchSize;
|
|
||||||
ulong divisor = 3UL;
|
ulong divisor = 3UL;
|
||||||
bool composite = false;
|
|
||||||
|
|
||||||
while (divisor <= allowedMax)
|
while (divisor <= allowedMax)
|
||||||
{
|
{
|
||||||
int batchSize = 0;
|
|
||||||
bool reachedEndInBatch = false;
|
|
||||||
|
|
||||||
while (batchSize < batchCapacity && divisor <= allowedMax)
|
|
||||||
{
|
|
||||||
ulong currentDivisor = divisor;
|
|
||||||
ulong nextDivisor = currentDivisor + 2UL;
|
|
||||||
|
|
||||||
divisors[batchSize++] = currentDivisor;
|
|
||||||
processedCount++;
|
processedCount++;
|
||||||
lastProcessed = currentDivisor;
|
lastProcessed = divisor;
|
||||||
|
|
||||||
if (nextDivisor <= currentDivisor)
|
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
||||||
|
byte hit;
|
||||||
|
if (useCycles)
|
||||||
{
|
{
|
||||||
reachedEndInBatch = true;
|
using DivisorCycleCache.Lease cycleLease = DivisorCycleCache.Shared.Acquire(divisor);
|
||||||
break;
|
ulong divisorCycle = cycleLease.GetCycle(divisor);
|
||||||
|
hit = CheckDivisor(prime, divisorCycle != 0UL, divisorCycle, divisorData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hit = CheckDivisor(prime, false, 0UL, divisorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextDivisor > allowedMax)
|
if (hit != 0)
|
||||||
{
|
|
||||||
divisor = nextDivisor;
|
|
||||||
reachedEndInBatch = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
divisor = nextDivisor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (batchSize == 0)
|
|
||||||
{
|
|
||||||
if (reachedEndInBatch)
|
|
||||||
{
|
{
|
||||||
processedAll = true;
|
processedAll = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
if (divisor >= allowedMax - 1UL)
|
||||||
}
|
|
||||||
|
|
||||||
Span<MontgomeryDivisorData> divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
|
||||||
Span<ulong> divisorSpan = divisors.AsSpan(0, batchSize);
|
|
||||||
Span<byte> hitsSpan = hits.AsSpan(0, batchSize);
|
|
||||||
|
|
||||||
for (int i = 0; i < batchSize; i++)
|
|
||||||
{
|
|
||||||
divisorDataSpan[i] = CreateMontgomeryDivisorData(divisorSpan[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < batchSize; i++)
|
|
||||||
{
|
|
||||||
hitsSpan[i] = CheckDivisor(prime, useCycles, divisorSpan[i], divisorDataSpan[i]);
|
|
||||||
if (hitsSpan[i] != 0)
|
|
||||||
{
|
|
||||||
composite = true;
|
|
||||||
lastProcessed = divisorSpan[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!composite)
|
|
||||||
{
|
|
||||||
lastProcessed = divisorSpan[batchSize - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (composite)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reachedEndInBatch)
|
|
||||||
{
|
{
|
||||||
processedAll = true;
|
processedAll = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (divisor > ulong.MaxValue - 2UL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processedAll = processedAll || divisor > allowedMax;
|
divisor += 2UL;
|
||||||
return composite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte CheckDivisor(ulong prime, bool useCycles, ulong divisor, in MontgomeryDivisorData divisorData)
|
if (!processedAll)
|
||||||
|
{
|
||||||
|
processedAll = divisor > allowedMax;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte CheckDivisor(ulong prime, bool useCycles, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong modulus = divisorData.Modulus;
|
ulong modulus = divisorData.Modulus;
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
@@ -278,26 +215,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong exponent = prime;
|
ulong residue = useCycles && divisorCycle != 0UL
|
||||||
|
? prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData)
|
||||||
|
: prime.Pow2MontgomeryMod(divisorData);
|
||||||
|
|
||||||
if (useCycles)
|
return residue == 1UL ? (byte)1 : (byte)0;
|
||||||
{
|
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(divisor);
|
|
||||||
if (cycle == 0UL)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong remainder = prime % cycle;
|
|
||||||
if (remainder != 0UL)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
exponent = remainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryMod(divisorData) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateStatusUnsafe(ulong lastProcessed, ulong processedCount)
|
private void UpdateStatusUnsafe(ulong lastProcessed, ulong processedCount)
|
||||||
@@ -380,19 +302,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
{
|
{
|
||||||
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
|
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
|
||||||
private ulong[] _primeBuffer;
|
|
||||||
private int[] _positionBuffer;
|
|
||||||
private ulong[] _exponentBuffer;
|
|
||||||
private int _capacity;
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
|
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
|
||||||
{
|
{
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
_capacity = Math.Max(1, owner._batchSize);
|
|
||||||
_primeBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
|
||||||
_positionBuffer = ArrayPool<int>.Shared.Rent(_capacity);
|
|
||||||
_exponentBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Reset()
|
internal void Reset()
|
||||||
@@ -407,116 +321,41 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
throw new ObjectDisposedException(nameof(DivisorScanSession));
|
throw new ObjectDisposedException(nameof(DivisorScanSession));
|
||||||
}
|
}
|
||||||
|
|
||||||
int primesLength = primes.Length;
|
int length = primes.Length;
|
||||||
if (primesLength == 0)
|
if (length == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MersenneNumberDivisorByDivisorCpuTester owner = _owner;
|
|
||||||
int batchSize = owner._batchSize;
|
|
||||||
EnsureCapacity(batchSize);
|
|
||||||
|
|
||||||
ulong modulus = divisor;
|
ulong modulus = divisor;
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
|
hits.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cycleEnabled = owner._useDivisorCycles && divisorCycle != 0UL;
|
bool cycleEnabled = _owner._useDivisorCycles && divisorCycle != 0UL;
|
||||||
ulong cycle = divisorCycle;
|
|
||||||
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
||||||
|
|
||||||
int offset = 0;
|
for (int i = 0; i < length; i++)
|
||||||
Span<ulong> primeSpan = _primeBuffer.AsSpan();
|
|
||||||
Span<int> positionSpan = _positionBuffer.AsSpan();
|
|
||||||
Span<ulong> exponentSpan = _exponentBuffer.AsSpan();
|
|
||||||
ulong previousResidue = 0UL;
|
|
||||||
ulong previousPrime = 0UL;
|
|
||||||
bool hasPreviousResidue = false;
|
|
||||||
|
|
||||||
while (offset < primesLength)
|
|
||||||
{
|
{
|
||||||
int sliceLength = Math.Min(batchSize, primesLength - offset);
|
ulong prime = primes[i];
|
||||||
ReadOnlySpan<ulong> primesSlice = primes.Slice(offset, sliceLength);
|
|
||||||
Span<byte> hitsSlice = hits.Slice(offset, sliceLength);
|
|
||||||
hitsSlice.Clear();
|
|
||||||
|
|
||||||
int computeCount = 0;
|
|
||||||
|
|
||||||
if (cycleEnabled)
|
if (cycleEnabled)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < sliceLength; i++)
|
ulong remainder = prime % divisorCycle;
|
||||||
{
|
if (remainder != 0UL)
|
||||||
ulong prime = primesSlice[i];
|
|
||||||
if (prime % cycle != 0UL)
|
|
||||||
{
|
{
|
||||||
|
hits[i] = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
primeSpan[computeCount] = prime;
|
|
||||||
positionSpan[computeCount] = i;
|
|
||||||
computeCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < sliceLength; i++)
|
|
||||||
{
|
|
||||||
primeSpan[computeCount] = primesSlice[i];
|
|
||||||
positionSpan[computeCount] = i;
|
|
||||||
computeCount++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (computeCount > 0)
|
ulong residue = cycleEnabled
|
||||||
{
|
? prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData)
|
||||||
Span<ulong> exponentSlice = exponentSpan[..computeCount];
|
: prime.Pow2MontgomeryMod(divisorData);
|
||||||
ulong currentPrime = hasPreviousResidue ? previousPrime : 0UL;
|
|
||||||
bool hasDeltaSource = hasPreviousResidue;
|
|
||||||
|
|
||||||
for (int i = 0; i < computeCount; i++)
|
hits[i] = residue == 1UL ? (byte)1 : (byte)0;
|
||||||
{
|
|
||||||
ulong prime = primeSpan[i];
|
|
||||||
ulong exponentValue;
|
|
||||||
if (hasDeltaSource)
|
|
||||||
{
|
|
||||||
exponentValue = prime - currentPrime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
exponentValue = prime;
|
|
||||||
hasDeltaSource = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cycleEnabled)
|
|
||||||
{
|
|
||||||
exponentValue %= cycle;
|
|
||||||
}
|
|
||||||
|
|
||||||
exponentSlice[i] = exponentValue;
|
|
||||||
currentPrime = prime;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < computeCount; i++)
|
|
||||||
{
|
|
||||||
exponentSlice[i] = exponentSlice[i].Pow2MontgomeryMod(divisorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < computeCount; i++)
|
|
||||||
{
|
|
||||||
int position = positionSpan[i];
|
|
||||||
ulong stepResidue = exponentSlice[i];
|
|
||||||
ulong prime = primeSpan[i];
|
|
||||||
ulong residue = hasPreviousResidue ? MultiplyMod(previousResidue, stepResidue, modulus) : stepResidue;
|
|
||||||
hitsSlice[position] = residue == 1UL ? (byte)1 : (byte)0;
|
|
||||||
previousResidue = residue;
|
|
||||||
previousPrime = prime;
|
|
||||||
hasPreviousResidue = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += sliceLength;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,34 +369,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
_disposed = true;
|
_disposed = true;
|
||||||
_owner.ReturnSession(this);
|
_owner.ReturnSession(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureCapacity(int requiredCapacity)
|
|
||||||
{
|
|
||||||
if (requiredCapacity <= _capacity)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayPool<ulong>.Shared.Return(_primeBuffer, clearArray: false);
|
|
||||||
ArrayPool<int>.Shared.Return(_positionBuffer, clearArray: false);
|
|
||||||
ArrayPool<ulong>.Shared.Return(_exponentBuffer, clearArray: false);
|
|
||||||
|
|
||||||
_capacity = requiredCapacity;
|
|
||||||
_primeBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
|
||||||
_positionBuffer = ArrayPool<int>.Shared.Rent(_capacity);
|
|
||||||
_exponentBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong MultiplyMod(ulong left, ulong right, ulong modulus)
|
|
||||||
{
|
|
||||||
if (modulus == 0UL)
|
|
||||||
{
|
|
||||||
return 0UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
UInt128 product = (UInt128)left * right;
|
|
||||||
return (ulong)(product % modulus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ILGPU;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
@@ -7,9 +13,6 @@ public sealed class DivisorCycleCache
|
|||||||
{
|
{
|
||||||
internal sealed class CycleBlock
|
internal sealed class CycleBlock
|
||||||
{
|
{
|
||||||
// TODO: This can be removed. See other comments.
|
|
||||||
private int _referenceCount;
|
|
||||||
|
|
||||||
internal CycleBlock(int index, ulong start, ulong[] cycles)
|
internal CycleBlock(int index, ulong start, ulong[] cycles)
|
||||||
{
|
{
|
||||||
Index = index;
|
Index = index;
|
||||||
@@ -18,7 +21,6 @@ public sealed class DivisorCycleCache
|
|||||||
End = start + (ulong)cycles.Length - 1UL;
|
End = start + (ulong)cycles.Length - 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Let's use fields instead of the properties below for better performance
|
|
||||||
internal int Index { get; }
|
internal int Index { get; }
|
||||||
|
|
||||||
internal ulong Start { get; }
|
internal ulong Start { get; }
|
||||||
@@ -27,27 +29,9 @@ public sealed class DivisorCycleCache
|
|||||||
|
|
||||||
internal ulong[] Cycles { get; }
|
internal ulong[] Cycles { get; }
|
||||||
|
|
||||||
// TODO: We don't need full reference tracking. It's enough to assign divisor block to a task, instead. We can remove excessive blocks as required.
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
internal void AddRef()
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref _referenceCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We don't need full reference tracking. It's enough to assign divisor block to a task, instead. We can remove excessive blocks as required.
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
internal void Release()
|
|
||||||
{
|
|
||||||
Interlocked.Decrement(ref _referenceCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We don't need full reference tracking. It's enough to assign divisor block to a task, instead. This can be removed
|
|
||||||
internal bool IsInUse => Volatile.Read(ref _referenceCount) > 0;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal ulong GetCycle(ulong divisor)
|
internal ulong GetCycle(ulong divisor)
|
||||||
{
|
{
|
||||||
// TODO: The production code should guarantee that this will never occur and we'll use divisors with their corresponding blocks, only. This can be removed and we can simplify this method to =>
|
|
||||||
if (divisor < Start || divisor > End)
|
if (divisor < Start || divisor > End)
|
||||||
{
|
{
|
||||||
return 0UL;
|
return 0UL;
|
||||||
@@ -59,21 +43,15 @@ public sealed class DivisorCycleCache
|
|||||||
|
|
||||||
public struct Lease : IDisposable
|
public struct Lease : IDisposable
|
||||||
{
|
{
|
||||||
// TODO: We can probably remove _owner, if we don't need full cycle management which will help GC to clear these out
|
|
||||||
private readonly DivisorCycleCache? _owner;
|
|
||||||
private CycleBlock? _block;
|
private CycleBlock? _block;
|
||||||
|
|
||||||
internal Lease(DivisorCycleCache owner, CycleBlock block)
|
internal Lease(CycleBlock block)
|
||||||
{
|
{
|
||||||
_owner = owner;
|
|
||||||
_block = block;
|
_block = block;
|
||||||
_block.AddRef();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We're highly over-complicating this. _block is always assigned by the constructor. We don't need to make it nullable. This can be removed.
|
|
||||||
public readonly bool IsValid => _block is not null;
|
public readonly bool IsValid => _block is not null;
|
||||||
|
|
||||||
// TODO: Once we make _block non-nullable we can simplify these to just fields assigned during the creation for the best performance.
|
|
||||||
public readonly ulong Start => _block?.Start ?? 0UL;
|
public readonly ulong Start => _block?.Start ?? 0UL;
|
||||||
|
|
||||||
public readonly ulong End => _block?.End ?? 0UL;
|
public readonly ulong End => _block?.End ?? 0UL;
|
||||||
@@ -85,32 +63,19 @@ public sealed class DivisorCycleCache
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// TODO: We don't need full cycle block management. We can rely on GC to do its job. Thanks to that we could possibly make the struct read-only.
|
|
||||||
CycleBlock? block = _block;
|
|
||||||
if (block is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
block.Release();
|
|
||||||
_owner?.Release(block);
|
|
||||||
_block = null;
|
_block = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object _sync = new();
|
private readonly object _sync = new();
|
||||||
private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new();
|
private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new();
|
||||||
// TODO: _baseBlock should be like no other block. Why do we need a separate property for it? It should become current upon initialization
|
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||||
private CycleBlock _baseBlock = null!;
|
private CycleBlock _baseBlock = null!;
|
||||||
private CycleBlock? _previous;
|
private CycleBlock? _activeBlock;
|
||||||
private CycleBlock? _current;
|
private CycleBlock? _prefetchedBlock;
|
||||||
private CycleBlock? _next;
|
private bool _useGpuGeneration = true;
|
||||||
private bool _initialized;
|
|
||||||
|
|
||||||
// TODO: Let's not use lazy but directly create the instance, instead for better performance. Then we can remove _lazy and just assign the instance to Shared.
|
public static DivisorCycleCache Shared { get; } = new DivisorCycleCache();
|
||||||
private static readonly Lazy<DivisorCycleCache> _lazy = new(() => new DivisorCycleCache());
|
|
||||||
|
|
||||||
public static DivisorCycleCache Shared => _lazy.Value;
|
|
||||||
|
|
||||||
private DivisorCycleCache()
|
private DivisorCycleCache()
|
||||||
{
|
{
|
||||||
@@ -124,44 +89,44 @@ public sealed class DivisorCycleCache
|
|||||||
Initialize(snapshot);
|
Initialize(snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ConfigureGeneratorDevice(bool useGpu)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_useGpuGeneration = useGpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Initialize(ulong[] snapshot)
|
private void Initialize(ulong[] snapshot)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
_pending.Clear();
|
_pending.Clear();
|
||||||
_baseBlock = new CycleBlock(index: 0, start: 0UL, snapshot);
|
_baseBlock = new CycleBlock(index: 0, start: 0UL, snapshot);
|
||||||
_previous = _baseBlock;
|
_activeBlock = null;
|
||||||
_current = null;
|
_prefetchedBlock = null;
|
||||||
_next = null;
|
|
||||||
_initialized = true;
|
|
||||||
StartPrefetchLocked(1);
|
StartPrefetchLocked(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Lease Acquire(ulong divisor)
|
public Lease Acquire(ulong divisor)
|
||||||
{
|
{
|
||||||
// TODO: We don't need _initialized. The constructor calls Initialize and makes sure it's always initialized, before use. This check can be removed, too.
|
|
||||||
if (!_initialized)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("DivisorCycleCache must be initialized before use.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Do we need Lease instances, if we don't care who is using our blocks once they were given and just remove them internally from future use?
|
|
||||||
if (divisor <= _baseBlock.End)
|
if (divisor <= _baseBlock.End)
|
||||||
{
|
{
|
||||||
return new Lease(this, _baseBlock);
|
return new Lease(_baseBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
CycleBlock block = AcquireDynamicBlock(divisor);
|
CycleBlock block = AcquireDynamicBlock(divisor);
|
||||||
return new Lease(this, block);
|
return new Lease(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CycleBlock AcquireDynamicBlock(ulong divisor)
|
private CycleBlock AcquireDynamicBlock(ulong divisor)
|
||||||
{
|
{
|
||||||
int blockIndex = GetBlockIndex(divisor);
|
int blockIndex = GetBlockIndex(divisor);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Task<CycleBlock>? taskToAwait = null;
|
Task<CycleBlock>? pendingTask = null;
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
if (TryGetCachedBlockLocked(blockIndex, out CycleBlock? cached))
|
if (TryGetCachedBlockLocked(blockIndex, out CycleBlock? cached))
|
||||||
@@ -169,14 +134,14 @@ public sealed class DivisorCycleCache
|
|||||||
return cached!;
|
return cached!;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_pending.TryGetValue(blockIndex, out taskToAwait))
|
if (!_pending.TryGetValue(blockIndex, out pendingTask))
|
||||||
{
|
{
|
||||||
taskToAwait = Task.Run(() => GenerateBlock(blockIndex));
|
pendingTask = Task.Run(() => GenerateBlock(blockIndex));
|
||||||
_pending[blockIndex] = taskToAwait;
|
_pending[blockIndex] = pendingTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CycleBlock block = taskToAwait.GetAwaiter().GetResult();
|
CycleBlock block = pendingTask.GetAwaiter().GetResult();
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
_pending.TryRemove(blockIndex, out _);
|
_pending.TryRemove(blockIndex, out _);
|
||||||
@@ -186,7 +151,7 @@ public sealed class DivisorCycleCache
|
|||||||
return cachedAfterAwait!;
|
return cachedAfterAwait!;
|
||||||
}
|
}
|
||||||
|
|
||||||
PromoteBlockLocked(block);
|
SetActiveBlockLocked(block);
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,22 +159,18 @@ public sealed class DivisorCycleCache
|
|||||||
|
|
||||||
private bool TryGetCachedBlockLocked(int blockIndex, out CycleBlock? block)
|
private bool TryGetCachedBlockLocked(int blockIndex, out CycleBlock? block)
|
||||||
{
|
{
|
||||||
if (_current is { Index: var currentIndex } current && currentIndex == blockIndex)
|
CycleBlock? active = _activeBlock;
|
||||||
|
if (active is not null && active.Index == blockIndex)
|
||||||
{
|
{
|
||||||
block = current;
|
block = active;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_previous is { Index: var previousIndex } previous && previousIndex == blockIndex)
|
CycleBlock? prefetched = _prefetchedBlock;
|
||||||
|
if (prefetched is not null && prefetched.Index == blockIndex)
|
||||||
{
|
{
|
||||||
block = previous;
|
PromotePrefetchedLocked();
|
||||||
return true;
|
block = _activeBlock!;
|
||||||
}
|
|
||||||
|
|
||||||
if (_next is { Index: var nextIndex } next && nextIndex == blockIndex)
|
|
||||||
{
|
|
||||||
PromoteNextLocked();
|
|
||||||
block = _current!;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,59 +178,41 @@ public sealed class DivisorCycleCache
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PromoteNextLocked()
|
private void PromotePrefetchedLocked()
|
||||||
{
|
{
|
||||||
if (_next is null)
|
CycleBlock? prefetched = _prefetchedBlock;
|
||||||
|
if (prefetched is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CycleBlock? oldPrevious = _previous;
|
_activeBlock = prefetched;
|
||||||
_previous = _current ?? _baseBlock;
|
_prefetchedBlock = null;
|
||||||
_current = _next;
|
StartPrefetchLocked(prefetched.Index + 1);
|
||||||
_next = null;
|
|
||||||
StartPrefetchLocked(_current!.Index + 1);
|
|
||||||
TryReleaseBlockLocked(oldPrevious);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PromoteBlockLocked(CycleBlock block)
|
private void SetActiveBlockLocked(CycleBlock block)
|
||||||
{
|
{
|
||||||
if (_current is null)
|
_activeBlock = block;
|
||||||
|
|
||||||
|
CycleBlock? prefetched = _prefetchedBlock;
|
||||||
|
if (prefetched is not null && prefetched.Index != block.Index + 1)
|
||||||
{
|
{
|
||||||
_previous = _baseBlock;
|
_prefetchedBlock = null;
|
||||||
_current = block;
|
}
|
||||||
|
|
||||||
StartPrefetchLocked(block.Index + 1);
|
StartPrefetchLocked(block.Index + 1);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.Index > _current.Index)
|
|
||||||
{
|
|
||||||
CycleBlock? oldPrevious = _previous;
|
|
||||||
_previous = _current;
|
|
||||||
_current = block;
|
|
||||||
StartPrefetchLocked(block.Index + 1);
|
|
||||||
TryReleaseBlockLocked(oldPrevious);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_previous is null || block.Index >= _previous.Index)
|
|
||||||
{
|
|
||||||
CycleBlock? oldPrevious = _previous;
|
|
||||||
_previous = block;
|
|
||||||
TryReleaseBlockLocked(oldPrevious);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartPrefetchLocked(int blockIndex)
|
private void StartPrefetchLocked(int blockIndex)
|
||||||
{
|
{
|
||||||
// TODO: Why do we need this check? This should never happen in production code.
|
|
||||||
if (blockIndex <= 0)
|
if (blockIndex <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Why do we need this check? This should never happen in production code.
|
CycleBlock? prefetched = _prefetchedBlock;
|
||||||
if (_next is { Index: var nextIndex } next && nextIndex == blockIndex)
|
if (prefetched is not null && prefetched.Index == blockIndex)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -278,12 +221,8 @@ public sealed class DivisorCycleCache
|
|||||||
{
|
{
|
||||||
if (existing.IsCompletedSuccessfully)
|
if (existing.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
if (_next is null || _next.Index < blockIndex)
|
|
||||||
{
|
|
||||||
_next = existing.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pending.TryRemove(blockIndex, out _);
|
_pending.TryRemove(blockIndex, out _);
|
||||||
|
AssignPrefetchedBlockLocked(blockIndex, existing.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -304,70 +243,22 @@ public sealed class DivisorCycleCache
|
|||||||
|
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
if (_next is null || _next.Index < blockIndex)
|
|
||||||
{
|
|
||||||
_next = task.Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pending.TryRemove(blockIndex, out _);
|
_pending.TryRemove(blockIndex, out _);
|
||||||
TrimCacheLocked();
|
AssignPrefetchedBlockLocked(blockIndex, task.Result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrimCacheLocked()
|
private void AssignPrefetchedBlockLocked(int blockIndex, CycleBlock block)
|
||||||
{
|
{
|
||||||
if (_previous is null)
|
CycleBlock? active = _activeBlock;
|
||||||
|
if (active is not null && blockIndex != active.Index + 1)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ReferenceEquals(_previous, _baseBlock))
|
if (_prefetchedBlock is null || _prefetchedBlock.Index < blockIndex)
|
||||||
{
|
{
|
||||||
return;
|
_prefetchedBlock = block;
|
||||||
}
|
|
||||||
|
|
||||||
if (_previous.IsInUse)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_next is not null && _current is not null)
|
|
||||||
{
|
|
||||||
_previous = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryReleaseBlockLocked(CycleBlock? block)
|
|
||||||
{
|
|
||||||
if (block is null || ReferenceEquals(block, _baseBlock))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ReferenceEquals(block, _current) || ReferenceEquals(block, _next))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.IsInUse)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ReferenceEquals(block, _previous))
|
|
||||||
{
|
|
||||||
_previous = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Release(CycleBlock block)
|
|
||||||
{
|
|
||||||
if (!block.IsInUse)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
TryReleaseBlockLocked(block);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,32 +270,34 @@ public sealed class DivisorCycleCache
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong start = GetBlockStart(blockIndex);
|
ulong start = GetBlockStart(blockIndex);
|
||||||
int length = GetBlockLength(blockIndex);
|
int length = _baseBlock.Cycles.Length;
|
||||||
ulong[] cycles = new ulong[length];
|
ulong[] cycles = new ulong[length];
|
||||||
ulong divisor = start;
|
|
||||||
for (int i = 0; i < length; i++)
|
if (_useGpuGeneration)
|
||||||
{
|
{
|
||||||
cycles[i] = MersenneDivisorCycles.CalculateCycleLength(divisor);
|
ComputeCyclesGpu(start, cycles);
|
||||||
divisor++;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ComputeCyclesCpu(start, cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CycleBlock(blockIndex, start, cycles);
|
return new CycleBlock(blockIndex, start, cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetBlockLength(int blockIndex)
|
private int GetBlockIndex(ulong divisor)
|
||||||
{
|
{
|
||||||
// TODO: We're over-complicating this again. We should just remember and use the _baseBlock.Cycles.Length. Remember it in a field for better performance during initialization / creation
|
if (divisor <= _baseBlock.End)
|
||||||
if (blockIndex == 0)
|
|
||||||
{
|
{
|
||||||
return _baseBlock.Cycles.Length;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PerfectNumberConstants.MaxQForDivisorCycles;
|
ulong offset = divisor - (_baseBlock.End + 1UL);
|
||||||
|
return (int)(offset / (ulong)PerfectNumberConstants.MaxQForDivisorCycles) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ulong GetBlockStart(int blockIndex)
|
private ulong GetBlockStart(int blockIndex)
|
||||||
{
|
{
|
||||||
// TODO: We're over-complicating this. There should be only 3 blocks kept. We should simply check all 3 to return the correct one, instead of expensive multiplications.
|
|
||||||
if (blockIndex == 0)
|
if (blockIndex == 0)
|
||||||
{
|
{
|
||||||
return 0UL;
|
return 0UL;
|
||||||
@@ -414,15 +307,72 @@ public sealed class DivisorCycleCache
|
|||||||
return baseEnd + 1UL + (ulong)(blockIndex - 1) * (ulong)PerfectNumberConstants.MaxQForDivisorCycles;
|
return baseEnd + 1UL + (ulong)(blockIndex - 1) * (ulong)PerfectNumberConstants.MaxQForDivisorCycles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetBlockIndex(ulong divisor)
|
private void ComputeCyclesCpu(ulong start, ulong[] destination)
|
||||||
{
|
{
|
||||||
// TODO: We're over-complicating this. There should be only 3 blocks kept. We should simply check all 3 to return the correct one, instead of expensive multiplications.
|
ulong divisor = start;
|
||||||
if (divisor <= _baseBlock.End)
|
for (int i = 0; i < destination.Length; i++)
|
||||||
{
|
{
|
||||||
return 0;
|
destination[i] = MersenneDivisorCycles.CalculateCycleLength(divisor);
|
||||||
|
divisor++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong offset = divisor - (_baseBlock.End + 1UL);
|
private void ComputeCyclesGpu(ulong start, ulong[] destination)
|
||||||
return (int)(offset / (ulong)PerfectNumberConstants.MaxQForDivisorCycles) + 1;
|
{
|
||||||
|
var gpuLease = GpuKernelPool.GetKernel(_useGpuGeneration);
|
||||||
|
var execution = gpuLease.EnterExecutionScope();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Accelerator accelerator = gpuLease.Accelerator;
|
||||||
|
Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel);
|
||||||
|
|
||||||
|
int length = destination.Length;
|
||||||
|
ulong[] divisors = ArrayPool<ulong>.Shared.Rent(length);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
divisors[i] = start + (ulong)i;
|
||||||
|
}
|
||||||
|
|
||||||
|
using MemoryBuffer1D<ulong, Stride1D.Dense> divisorBuffer = accelerator.Allocate1D<ulong>(length);
|
||||||
|
using MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = accelerator.Allocate1D<ulong>(length);
|
||||||
|
|
||||||
|
divisorBuffer.View.CopyFromCPU(ref divisors[0], length);
|
||||||
|
kernel(length, divisorBuffer.View, resultBuffer.View);
|
||||||
|
accelerator.Synchronize();
|
||||||
|
resultBuffer.View.CopyToCPU(ref destination[0], length);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<ulong>.Shared.Return(divisors, clearArray: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
execution.Dispose();
|
||||||
|
gpuLease.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> LoadKernel(Accelerator accelerator)
|
||||||
|
{
|
||||||
|
return accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(GpuDivisorCycleKernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GpuDivisorCycleKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> divisors, ArrayView1D<ulong, Stride1D.Dense> cycles)
|
||||||
|
{
|
||||||
|
cycles[index] = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
|
||||||
|
{
|
||||||
|
internal static AcceleratorReferenceComparer Instance { get; } = new();
|
||||||
|
|
||||||
|
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
|
||||||
|
|
||||||
|
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
UpdateStatusUnsafe(lastProcessed, processedCount);
|
UpdateStatusUnsafe(processedCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +174,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
ulong currentDivisor, divisor = 3UL, next, nextDivisor;
|
ulong currentDivisor, divisor = 3UL, next, nextDivisor;
|
||||||
int batchSize, i;
|
int batchSize, i;
|
||||||
bool reachedEndInBatch;
|
bool reachedEndInBatch;
|
||||||
|
bool requiresCycleKernelCheck;
|
||||||
|
|
||||||
Span<MontgomeryDivisorData> divisorDataSpan;
|
Span<MontgomeryDivisorData> divisorDataSpan;
|
||||||
Span<ulong> divisorSpan;
|
Span<ulong> divisorSpan;
|
||||||
@@ -185,6 +186,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
{
|
{
|
||||||
batchSize = 0;
|
batchSize = 0;
|
||||||
reachedEndInBatch = false;
|
reachedEndInBatch = false;
|
||||||
|
requiresCycleKernelCheck = false;
|
||||||
|
|
||||||
while (batchSize < batchCapacity && divisor <= allowedMax)
|
while (batchSize < batchCapacity && divisor <= allowedMax)
|
||||||
{
|
{
|
||||||
@@ -192,11 +194,29 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
nextDivisor = currentDivisor + 2UL;
|
nextDivisor = currentDivisor + 2UL;
|
||||||
processedCount++;
|
processedCount++;
|
||||||
|
|
||||||
|
bool includeDivisor = true;
|
||||||
|
if (useCycles)
|
||||||
|
{
|
||||||
|
using DivisorCycleCache.Lease cycleLease = DivisorCycleCache.Shared.Acquire(currentDivisor);
|
||||||
|
ulong cycle = cycleLease.GetCycle(currentDivisor);
|
||||||
|
if (cycle == 0UL)
|
||||||
|
{
|
||||||
|
requiresCycleKernelCheck = true;
|
||||||
|
}
|
||||||
|
else if (prime % cycle != 0UL)
|
||||||
|
{
|
||||||
|
includeDivisor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeDivisor)
|
||||||
|
{
|
||||||
divisors[batchSize++] = currentDivisor;
|
divisors[batchSize++] = currentDivisor;
|
||||||
|
}
|
||||||
|
|
||||||
lastProcessed = currentDivisor;
|
lastProcessed = currentDivisor;
|
||||||
next = nextDivisor;
|
next = nextDivisor;
|
||||||
|
|
||||||
// This will be only true when we exceed ulong.MaxValue
|
|
||||||
if (next <= divisor)
|
if (next <= divisor)
|
||||||
{
|
{
|
||||||
reachedEndInBatch = true;
|
reachedEndInBatch = true;
|
||||||
@@ -218,9 +238,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
if (reachedEndInBatch)
|
if (reachedEndInBatch)
|
||||||
{
|
{
|
||||||
processedAll = true;
|
processedAll = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
||||||
@@ -236,7 +257,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
||||||
kernel(batchSize, prime, useCycles ? (byte)1 : (byte)0, divisorView, hitsView);
|
byte useCycleChecks = useCycles && requiresCycleKernelCheck ? (byte)1 : (byte)0;
|
||||||
|
kernel(batchSize, prime, useCycleChecks, divisorView, hitsView);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize);
|
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize);
|
||||||
|
|
||||||
@@ -301,8 +323,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Since lastProcessed is unused, let's remove it.
|
private void UpdateStatusUnsafe(ulong processedCount)
|
||||||
private void UpdateStatusUnsafe(ulong lastProcessed, ulong processedCount)
|
|
||||||
{
|
{
|
||||||
if (processedCount == 0UL)
|
if (processedCount == 0UL)
|
||||||
{
|
{
|
||||||
@@ -340,7 +361,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
hits[index] = 0;
|
hits[index] = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: Can we move this back before the GPU call and let CPU handle it? We significantly slowed down after this was moved to GPU.
|
|
||||||
ulong remainder = prime % cycle;
|
ulong remainder = prime % cycle;
|
||||||
if (remainder != 0UL)
|
if (remainder != 0UL)
|
||||||
{
|
{
|
||||||
@@ -456,9 +476,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
Span<ulong> hostSpan = _hostBuffer.AsSpan();
|
Span<ulong> hostSpan = _hostBuffer.AsSpan();
|
||||||
Span<ulong> primeSpan = _primeBuffer.AsSpan();
|
Span<ulong> primeSpan = _primeBuffer.AsSpan();
|
||||||
Span<int> positionSpan = _positionBuffer.AsSpan();
|
Span<int> positionSpan = _positionBuffer.AsSpan();
|
||||||
bool hasPreviousResidue = false;
|
|
||||||
ulong previousResidue = 0UL;
|
|
||||||
ulong previousPrime = 0UL;
|
|
||||||
|
|
||||||
while (offset < primesLength)
|
while (offset < primesLength)
|
||||||
{
|
{
|
||||||
@@ -467,63 +484,47 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
Span<byte> hitsSlice = hits.Slice(offset, batchSize);
|
Span<byte> hitsSlice = hits.Slice(offset, batchSize);
|
||||||
|
|
||||||
int computeCount = 0;
|
int computeCount = 0;
|
||||||
Span<byte> cycleMaskSpan = default;
|
|
||||||
|
|
||||||
if (cycleEnabled)
|
if (cycleEnabled)
|
||||||
{
|
{
|
||||||
// TODO: Can we move this back before the GPU call and let CPU handle it? We significantly slowed down after this was moved to GPU.
|
Span<byte> cycleMaskSpan = _cycleMaskHost.AsSpan(0, batchSize);
|
||||||
cycleMaskSpan = _cycleMaskHost.AsSpan(0, batchSize);
|
|
||||||
ArrayView1D<ulong, Stride1D.Dense> primeInputView = _resultsBuffer.View.SubView(0, batchSize);
|
ArrayView1D<ulong, Stride1D.Dense> primeInputView = _resultsBuffer.View.SubView(0, batchSize);
|
||||||
primeInputView.CopyFromCPU(ref MemoryMarshal.GetReference(primesSlice), batchSize);
|
primeInputView.CopyFromCPU(ref MemoryMarshal.GetReference(primesSlice), batchSize);
|
||||||
ArrayView1D<byte, Stride1D.Dense> cycleMaskView = cycleMaskViewFull.SubView(0, batchSize);
|
ArrayView1D<byte, Stride1D.Dense> cycleMaskView = cycleMaskViewFull.SubView(0, batchSize);
|
||||||
cycleKernel(batchSize, cycle, primeInputView, cycleMaskView);
|
cycleKernel(batchSize, cycle, primeInputView, cycleMaskView);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
cycleMaskView.CopyToCPU(ref MemoryMarshal.GetReference(cycleMaskSpan), batchSize);
|
cycleMaskView.CopyToCPU(ref MemoryMarshal.GetReference(cycleMaskSpan), batchSize);
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < batchSize; i++)
|
for (int i = 0; i < batchSize; i++)
|
||||||
{
|
{
|
||||||
if (cycleEnabled && cycleMaskSpan[i] == 0)
|
if (cycleMaskSpan[i] == 0)
|
||||||
{
|
{
|
||||||
hitsSlice[i] = 0;
|
hitsSlice[i] = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Avoid one-time use variables like prime here.
|
primeSpan[computeCount] = primesSlice[i];
|
||||||
ulong prime = primesSlice[i];
|
|
||||||
primeSpan[computeCount] = prime;
|
|
||||||
positionSpan[computeCount] = i;
|
positionSpan[computeCount] = i;
|
||||||
computeCount++;
|
computeCount++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < batchSize; i++)
|
||||||
|
{
|
||||||
|
primeSpan[computeCount] = primesSlice[i];
|
||||||
|
positionSpan[computeCount] = i;
|
||||||
|
computeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (computeCount > 0)
|
if (computeCount > 0)
|
||||||
{
|
{
|
||||||
Span<ulong> exponentSlice = hostSpan[..computeCount];
|
Span<ulong> exponentSlice = hostSpan[..computeCount];
|
||||||
ulong currentPrime = hasPreviousResidue ? previousPrime : 0UL;
|
|
||||||
bool hasDeltaSource = hasPreviousResidue;
|
|
||||||
|
|
||||||
for (int i = 0; i < computeCount; i++)
|
for (int i = 0; i < computeCount; i++)
|
||||||
{
|
{
|
||||||
ulong prime = primeSpan[i];
|
ulong prime = primeSpan[i];
|
||||||
ulong exponentValue;
|
exponentSlice[i] = cycleEnabled ? prime % cycle : prime;
|
||||||
if (hasDeltaSource)
|
|
||||||
{
|
|
||||||
exponentValue = prime - currentPrime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
exponentValue = prime;
|
|
||||||
hasDeltaSource = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cycleEnabled)
|
|
||||||
{
|
|
||||||
exponentValue %= cycle;
|
|
||||||
}
|
|
||||||
|
|
||||||
exponentSlice[i] = exponentValue;
|
|
||||||
|
|
||||||
currentPrime = prime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentsView.SubView(0, computeCount);
|
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentsView.SubView(0, computeCount);
|
||||||
@@ -533,29 +534,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
resultView.CopyToCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
resultView.CopyToCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
||||||
|
|
||||||
ulong residueAccumulator = previousResidue;
|
|
||||||
ulong lastPrime = previousPrime;
|
|
||||||
bool hasAccumulator = hasPreviousResidue;
|
|
||||||
|
|
||||||
for (int i = 0; i < computeCount; i++)
|
for (int i = 0; i < computeCount; i++)
|
||||||
{
|
{
|
||||||
// TODO: Avoid one-time use variables like prime and position here.
|
|
||||||
int position = positionSpan[i];
|
int position = positionSpan[i];
|
||||||
ulong stepResidue = exponentSlice[i];
|
ulong residue = exponentSlice[i];
|
||||||
ulong prime = primeSpan[i];
|
|
||||||
ulong residue = hasAccumulator ? MultiplyMod(residueAccumulator, stepResidue, modulus) : stepResidue;
|
|
||||||
hitsSlice[position] = residue == 1UL ? (byte)1 : (byte)0;
|
hitsSlice[position] = residue == 1UL ? (byte)1 : (byte)0;
|
||||||
exponentSlice[i] = residue;
|
|
||||||
residueAccumulator = residue;
|
|
||||||
lastPrime = prime;
|
|
||||||
hasAccumulator = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasAccumulator)
|
|
||||||
{
|
|
||||||
previousResidue = residueAccumulator;
|
|
||||||
previousPrime = lastPrime;
|
|
||||||
hasPreviousResidue = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,16 +557,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
_owner.ReturnSession(this);
|
_owner.ReturnSession(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong MultiplyMod(ulong left, ulong right, ulong modulus)
|
|
||||||
{
|
|
||||||
if (modulus == 0UL)
|
|
||||||
{
|
|
||||||
return 0UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
UInt128 product = (UInt128)left * right;
|
|
||||||
return (ulong)(product % modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -246,6 +246,23 @@ public static class ULongExtensions
|
|||||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2MontgomeryModWithCycle(this ulong exponent, ulong cycleLength, in MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData divisor)
|
||||||
|
{
|
||||||
|
if (cycleLength == 0UL)
|
||||||
|
{
|
||||||
|
return exponent.Pow2MontgomeryMod(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong rotationCount = exponent % cycleLength;
|
||||||
|
if (rotationCount == 0UL)
|
||||||
|
{
|
||||||
|
return 1UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rotationCount.Pow2MontgomeryMod(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user