Restore cycle-aware divisor scans and background generation
This commit is contained in:
1 parent
bc4e526946
commit
b798970ce9
6 files changed
+1501
-1747
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,7 +457,8 @@ internal static class Program
|
|||||||
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
|
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
DivisorCycleCache.Shared.ReloadFromCurrentSnapshot();
|
DivisorCycleCache.Shared.ConfigureGeneratorDevice(orderOnGpu);
|
||||||
|
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,556 +7,368 @@ 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();
|
||||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
||||||
private ulong _divisorLimit;
|
private ulong _divisorLimit;
|
||||||
private ulong _lastStatusDivisor;
|
private ulong _lastStatusDivisor;
|
||||||
private bool _isConfigured;
|
private bool _isConfigured;
|
||||||
private bool _useDivisorCycles;
|
private bool _useDivisorCycles;
|
||||||
private int _batchSize = 1_024;
|
private int _batchSize = 1_024;
|
||||||
|
|
||||||
public bool UseDivisorCycles
|
public bool UseDivisorCycles
|
||||||
{
|
{
|
||||||
get => _useDivisorCycles;
|
get => _useDivisorCycles;
|
||||||
set => _useDivisorCycles = value;
|
set => _useDivisorCycles = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int BatchSize
|
public int BatchSize
|
||||||
{
|
{
|
||||||
get => _batchSize;
|
get => _batchSize;
|
||||||
set => _batchSize = Math.Max(1, value);
|
set => _batchSize = Math.Max(1, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||||
_lastStatusDivisor = 0UL;
|
_lastStatusDivisor = 0UL;
|
||||||
_isConfigured = true;
|
_isConfigured = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return _divisorLimit;
|
return _divisorLimit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||||
{
|
{
|
||||||
ulong allowedMax;
|
ulong allowedMax;
|
||||||
bool useCycles;
|
bool useCycles;
|
||||||
int batchCapacity;
|
|
||||||
|
lock (_sync)
|
||||||
lock (_sync)
|
{
|
||||||
{
|
if (!_isConfigured)
|
||||||
if (!_isConfigured)
|
{
|
||||||
{
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
}
|
||||||
}
|
|
||||||
|
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
useCycles = _useDivisorCycles;
|
||||||
useCycles = _useDivisorCycles;
|
}
|
||||||
batchCapacity = _batchSize;
|
|
||||||
}
|
if (allowedMax < 3UL)
|
||||||
|
{
|
||||||
if (allowedMax < 3UL)
|
divisorsExhausted = true;
|
||||||
{
|
return true;
|
||||||
divisorsExhausted = true;
|
}
|
||||||
return true;
|
|
||||||
}
|
ulong processedCount = 0UL;
|
||||||
|
ulong lastProcessed = 0UL;
|
||||||
ulong[] divisors = ArrayPool<ulong>.Shared.Rent(batchCapacity);
|
bool processedAll = false;
|
||||||
byte[] hits = ArrayPool<byte>.Shared.Rent(batchCapacity);
|
|
||||||
MontgomeryDivisorData[] divisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(batchCapacity);
|
bool composite = CheckDivisors(
|
||||||
|
prime,
|
||||||
ulong processedCount = 0UL;
|
allowedMax,
|
||||||
ulong lastProcessed = 0UL;
|
useCycles,
|
||||||
bool composite = false;
|
out lastProcessed,
|
||||||
bool processedAll = false;
|
out processedAll,
|
||||||
|
out processedCount);
|
||||||
try
|
|
||||||
{
|
if (processedCount > 0UL)
|
||||||
composite = CheckDivisors(
|
{
|
||||||
prime,
|
lock (_sync)
|
||||||
allowedMax,
|
{
|
||||||
useCycles,
|
UpdateStatusUnsafe(lastProcessed, processedCount);
|
||||||
divisors,
|
}
|
||||||
hits,
|
}
|
||||||
divisorData,
|
|
||||||
out lastProcessed,
|
if (composite)
|
||||||
out processedAll,
|
{
|
||||||
out processedCount);
|
divisorsExhausted = true;
|
||||||
}
|
return false;
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
ArrayPool<ulong>.Shared.Return(divisors, clearArray: false);
|
divisorsExhausted = processedAll || composite;
|
||||||
ArrayPool<byte>.Shared.Return(hits, clearArray: false);
|
return true;
|
||||||
ArrayPool<MontgomeryDivisorData>.Shared.Return(divisorData, clearArray: false);
|
}
|
||||||
}
|
|
||||||
|
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||||
if (processedCount > 0UL)
|
{
|
||||||
{
|
lock (_sync)
|
||||||
lock (_sync)
|
{
|
||||||
{
|
if (!_isConfigured)
|
||||||
UpdateStatusUnsafe(lastProcessed, processedCount);
|
{
|
||||||
}
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (composite)
|
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
session.Reset();
|
||||||
return false;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
divisorsExhausted = processedAll || composite;
|
return new DivisorScanSession(this);
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
private void ReturnSession(DivisorScanSession session)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
_sessionPool.Add(session);
|
||||||
{
|
}
|
||||||
if (!_isConfigured)
|
|
||||||
{
|
private bool CheckDivisors(
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
ulong prime,
|
||||||
}
|
ulong allowedMax,
|
||||||
|
bool useCycles,
|
||||||
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
out ulong lastProcessed,
|
||||||
{
|
out bool processedAll,
|
||||||
session.Reset();
|
out ulong processedCount)
|
||||||
return session;
|
{
|
||||||
}
|
lastProcessed = 0UL;
|
||||||
|
processedCount = 0UL;
|
||||||
return new DivisorScanSession(this);
|
processedAll = false;
|
||||||
}
|
|
||||||
}
|
if (allowedMax < 3UL)
|
||||||
|
{
|
||||||
private void ReturnSession(DivisorScanSession session)
|
return false;
|
||||||
{
|
}
|
||||||
_sessionPool.Add(session);
|
|
||||||
}
|
ulong divisor = 3UL;
|
||||||
|
while (divisor <= allowedMax)
|
||||||
private bool CheckDivisors(
|
{
|
||||||
ulong prime,
|
processedCount++;
|
||||||
ulong allowedMax,
|
lastProcessed = divisor;
|
||||||
bool useCycles,
|
|
||||||
ulong[] divisors,
|
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
||||||
byte[] hits,
|
byte hit;
|
||||||
MontgomeryDivisorData[] divisorData,
|
if (useCycles)
|
||||||
out ulong lastProcessed,
|
{
|
||||||
out bool processedAll,
|
using DivisorCycleCache.Lease cycleLease = DivisorCycleCache.Shared.Acquire(divisor);
|
||||||
out ulong processedCount)
|
ulong divisorCycle = cycleLease.GetCycle(divisor);
|
||||||
{
|
hit = CheckDivisor(prime, divisorCycle != 0UL, divisorCycle, divisorData);
|
||||||
lastProcessed = 0UL;
|
}
|
||||||
processedCount = 0UL;
|
else
|
||||||
processedAll = false;
|
{
|
||||||
|
hit = CheckDivisor(prime, false, 0UL, divisorData);
|
||||||
if (allowedMax < 3UL)
|
}
|
||||||
{
|
|
||||||
return false;
|
if (hit != 0)
|
||||||
}
|
{
|
||||||
|
processedAll = true;
|
||||||
int batchCapacity = _batchSize;
|
return true;
|
||||||
ulong divisor = 3UL;
|
}
|
||||||
bool composite = false;
|
|
||||||
|
if (divisor >= allowedMax - 1UL)
|
||||||
while (divisor <= allowedMax)
|
{
|
||||||
{
|
processedAll = true;
|
||||||
int batchSize = 0;
|
break;
|
||||||
bool reachedEndInBatch = false;
|
}
|
||||||
|
|
||||||
while (batchSize < batchCapacity && divisor <= allowedMax)
|
if (divisor > ulong.MaxValue - 2UL)
|
||||||
{
|
{
|
||||||
ulong currentDivisor = divisor;
|
break;
|
||||||
ulong nextDivisor = currentDivisor + 2UL;
|
}
|
||||||
|
|
||||||
divisors[batchSize++] = currentDivisor;
|
divisor += 2UL;
|
||||||
processedCount++;
|
}
|
||||||
lastProcessed = currentDivisor;
|
|
||||||
|
if (!processedAll)
|
||||||
if (nextDivisor <= currentDivisor)
|
{
|
||||||
{
|
processedAll = divisor > allowedMax;
|
||||||
reachedEndInBatch = true;
|
}
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextDivisor > allowedMax)
|
private static byte CheckDivisor(ulong prime, bool useCycles, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
divisor = nextDivisor;
|
ulong modulus = divisorData.Modulus;
|
||||||
reachedEndInBatch = true;
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
break;
|
{
|
||||||
}
|
return 0;
|
||||||
|
}
|
||||||
divisor = nextDivisor;
|
|
||||||
}
|
ulong residue = useCycles && divisorCycle != 0UL
|
||||||
|
? prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData)
|
||||||
if (batchSize == 0)
|
: prime.Pow2MontgomeryMod(divisorData);
|
||||||
{
|
|
||||||
if (reachedEndInBatch)
|
return residue == 1UL ? (byte)1 : (byte)0;
|
||||||
{
|
}
|
||||||
processedAll = true;
|
|
||||||
}
|
private void UpdateStatusUnsafe(ulong lastProcessed, ulong processedCount)
|
||||||
|
{
|
||||||
break;
|
if (processedCount == 0UL)
|
||||||
}
|
{
|
||||||
|
return;
|
||||||
Span<MontgomeryDivisorData> divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
}
|
||||||
Span<ulong> divisorSpan = divisors.AsSpan(0, batchSize);
|
|
||||||
Span<byte> hitsSpan = hits.AsSpan(0, batchSize);
|
ulong interval = PerfectNumberConstants.ConsoleInterval;
|
||||||
|
if (interval == 0UL)
|
||||||
for (int i = 0; i < batchSize; i++)
|
{
|
||||||
{
|
_lastStatusDivisor = 0UL;
|
||||||
divisorDataSpan[i] = CreateMontgomeryDivisorData(divisorSpan[i]);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < batchSize; i++)
|
ulong total = _lastStatusDivisor + processedCount;
|
||||||
{
|
_lastStatusDivisor = total % interval;
|
||||||
hitsSpan[i] = CheckDivisor(prime, useCycles, divisorSpan[i], divisorDataSpan[i]);
|
}
|
||||||
if (hitsSpan[i] != 0)
|
|
||||||
{
|
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
||||||
composite = true;
|
{
|
||||||
lastProcessed = divisorSpan[i];
|
if (maxPrime <= 1UL)
|
||||||
break;
|
{
|
||||||
}
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!composite)
|
if (maxPrime - 1UL >= 64UL)
|
||||||
{
|
{
|
||||||
lastProcessed = divisorSpan[batchSize - 1];
|
return ulong.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (composite)
|
return (1UL << (int)(maxPrime - 1UL)) - 1UL;
|
||||||
{
|
}
|
||||||
break;
|
|
||||||
}
|
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
|
||||||
|
{
|
||||||
if (reachedEndInBatch)
|
if (prime <= 1UL)
|
||||||
{
|
{
|
||||||
processedAll = true;
|
return 0UL;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
}
|
if (prime - 1UL >= 64UL)
|
||||||
|
{
|
||||||
processedAll = processedAll || divisor > allowedMax;
|
return divisorLimit;
|
||||||
return composite;
|
}
|
||||||
}
|
|
||||||
|
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||||
private static byte CheckDivisor(ulong prime, bool useCycles, ulong divisor, in MontgomeryDivisorData divisorData)
|
}
|
||||||
{
|
|
||||||
ulong modulus = divisorData.Modulus;
|
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
{
|
||||||
{
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
return 0;
|
{
|
||||||
}
|
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL);
|
||||||
|
}
|
||||||
ulong exponent = prime;
|
|
||||||
|
return new MontgomeryDivisorData(
|
||||||
if (useCycles)
|
modulus,
|
||||||
{
|
ComputeMontgomeryNPrime(modulus),
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(divisor);
|
ComputeMontgomeryResidue(1UL, modulus),
|
||||||
if (cycle == 0UL)
|
ComputeMontgomeryResidue(2UL, modulus));
|
||||||
{
|
}
|
||||||
return 0;
|
|
||||||
}
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
||||||
ulong remainder = prime % cycle;
|
|
||||||
if (remainder != 0UL)
|
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||||||
{
|
{
|
||||||
return 0;
|
ulong inv = modulus;
|
||||||
}
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
exponent = remainder;
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
}
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
return exponent.Pow2MontgomeryMod(divisorData) == 1UL ? (byte)1 : (byte)0;
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
}
|
return unchecked(0UL - inv);
|
||||||
|
}
|
||||||
private void UpdateStatusUnsafe(ulong lastProcessed, ulong processedCount)
|
|
||||||
{
|
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
if (processedCount == 0UL)
|
{
|
||||||
{
|
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
|
||||||
return;
|
private bool _disposed;
|
||||||
}
|
|
||||||
|
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
|
||||||
ulong interval = PerfectNumberConstants.ConsoleInterval;
|
{
|
||||||
if (interval == 0UL)
|
_owner = owner;
|
||||||
{
|
}
|
||||||
_lastStatusDivisor = 0UL;
|
|
||||||
return;
|
internal void Reset()
|
||||||
}
|
{
|
||||||
|
_disposed = false;
|
||||||
ulong total = _lastStatusDivisor + processedCount;
|
}
|
||||||
_lastStatusDivisor = total % interval;
|
|
||||||
}
|
public void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||||
|
{
|
||||||
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
if (maxPrime <= 1UL)
|
throw new ObjectDisposedException(nameof(DivisorScanSession));
|
||||||
{
|
}
|
||||||
return 0UL;
|
|
||||||
}
|
int length = primes.Length;
|
||||||
|
if (length == 0)
|
||||||
if (maxPrime - 1UL >= 64UL)
|
{
|
||||||
{
|
return;
|
||||||
return ulong.MaxValue;
|
}
|
||||||
}
|
|
||||||
|
ulong modulus = divisor;
|
||||||
return (1UL << (int)(maxPrime - 1UL)) - 1UL;
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
}
|
{
|
||||||
|
hits.Clear();
|
||||||
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
|
return;
|
||||||
{
|
}
|
||||||
if (prime <= 1UL)
|
|
||||||
{
|
bool cycleEnabled = _owner._useDivisorCycles && divisorCycle != 0UL;
|
||||||
return 0UL;
|
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
||||||
}
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
if (prime - 1UL >= 64UL)
|
{
|
||||||
{
|
ulong prime = primes[i];
|
||||||
return divisorLimit;
|
|
||||||
}
|
if (cycleEnabled)
|
||||||
|
{
|
||||||
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
ulong remainder = prime % divisorCycle;
|
||||||
}
|
if (remainder != 0UL)
|
||||||
|
{
|
||||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
hits[i] = 0;
|
||||||
{
|
continue;
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
}
|
||||||
{
|
}
|
||||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL);
|
|
||||||
}
|
ulong residue = cycleEnabled
|
||||||
|
? prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData)
|
||||||
return new MontgomeryDivisorData(
|
: prime.Pow2MontgomeryMod(divisorData);
|
||||||
modulus,
|
|
||||||
ComputeMontgomeryNPrime(modulus),
|
hits[i] = residue == 1UL ? (byte)1 : (byte)0;
|
||||||
ComputeMontgomeryResidue(1UL, modulus),
|
}
|
||||||
ComputeMontgomeryResidue(2UL, modulus));
|
}
|
||||||
}
|
|
||||||
|
public void Dispose()
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
{
|
||||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
if (_disposed)
|
||||||
|
{
|
||||||
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
return;
|
||||||
{
|
}
|
||||||
ulong inv = modulus;
|
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
_disposed = true;
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
_owner.ReturnSession(this);
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
}
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
}
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
|
||||||
inv *= unchecked(2UL - modulus * inv);
|
|
||||||
return unchecked(0UL - inv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
|
||||||
{
|
|
||||||
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
|
|
||||||
private ulong[] _primeBuffer;
|
|
||||||
private int[] _positionBuffer;
|
|
||||||
private ulong[] _exponentBuffer;
|
|
||||||
private int _capacity;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester 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()
|
|
||||||
{
|
|
||||||
_disposed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
throw new ObjectDisposedException(nameof(DivisorScanSession));
|
|
||||||
}
|
|
||||||
|
|
||||||
int primesLength = primes.Length;
|
|
||||||
if (primesLength == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MersenneNumberDivisorByDivisorCpuTester owner = _owner;
|
|
||||||
int batchSize = owner._batchSize;
|
|
||||||
EnsureCapacity(batchSize);
|
|
||||||
|
|
||||||
ulong modulus = divisor;
|
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cycleEnabled = owner._useDivisorCycles && divisorCycle != 0UL;
|
|
||||||
ulong cycle = divisorCycle;
|
|
||||||
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
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);
|
|
||||||
ReadOnlySpan<ulong> primesSlice = primes.Slice(offset, sliceLength);
|
|
||||||
Span<byte> hitsSlice = hits.Slice(offset, sliceLength);
|
|
||||||
hitsSlice.Clear();
|
|
||||||
|
|
||||||
int computeCount = 0;
|
|
||||||
|
|
||||||
if (cycleEnabled)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < sliceLength; i++)
|
|
||||||
{
|
|
||||||
ulong prime = primesSlice[i];
|
|
||||||
if (prime % cycle != 0UL)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Span<ulong> exponentSlice = exponentSpan[..computeCount];
|
|
||||||
ulong currentPrime = hasPreviousResidue ? previousPrime : 0UL;
|
|
||||||
bool hasDeltaSource = hasPreviousResidue;
|
|
||||||
|
|
||||||
for (int i = 0; i < computeCount; i++)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
_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,428 +1,378 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
public sealed class DivisorCycleCache
|
public sealed class DivisorCycleCache
|
||||||
{
|
{
|
||||||
internal sealed class CycleBlock
|
internal sealed class CycleBlock
|
||||||
{
|
{
|
||||||
// TODO: This can be removed. See other comments.
|
internal CycleBlock(int index, ulong start, ulong[] cycles)
|
||||||
private int _referenceCount;
|
{
|
||||||
|
Index = index;
|
||||||
internal CycleBlock(int index, ulong start, ulong[] cycles)
|
Start = start;
|
||||||
{
|
Cycles = cycles;
|
||||||
Index = index;
|
End = start + (ulong)cycles.Length - 1UL;
|
||||||
Start = start;
|
}
|
||||||
Cycles = cycles;
|
|
||||||
End = start + (ulong)cycles.Length - 1UL;
|
internal int Index { get; }
|
||||||
}
|
|
||||||
|
internal ulong Start { get; }
|
||||||
// TODO: Let's use fields instead of the properties below for better performance
|
|
||||||
internal int Index { get; }
|
internal ulong End { get; }
|
||||||
|
|
||||||
internal ulong Start { get; }
|
internal ulong[] Cycles { get; }
|
||||||
|
|
||||||
internal ulong End { get; }
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal ulong GetCycle(ulong divisor)
|
||||||
internal ulong[] Cycles { get; }
|
{
|
||||||
|
if (divisor < Start || divisor > End)
|
||||||
// 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)]
|
return 0UL;
|
||||||
internal void AddRef()
|
}
|
||||||
{
|
|
||||||
Interlocked.Increment(ref _referenceCount);
|
return Cycles[(int)(divisor - Start)];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 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)]
|
public struct Lease : IDisposable
|
||||||
internal void Release()
|
{
|
||||||
{
|
private CycleBlock? _block;
|
||||||
Interlocked.Decrement(ref _referenceCount);
|
|
||||||
}
|
internal Lease(CycleBlock block)
|
||||||
|
{
|
||||||
// TODO: We don't need full reference tracking. It's enough to assign divisor block to a task, instead. This can be removed
|
_block = block;
|
||||||
internal bool IsInUse => Volatile.Read(ref _referenceCount) > 0;
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public readonly bool IsValid => _block is not null;
|
||||||
internal ulong GetCycle(ulong divisor)
|
|
||||||
{
|
public readonly ulong Start => _block?.Start ?? 0UL;
|
||||||
// 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)
|
public readonly ulong End => _block?.End ?? 0UL;
|
||||||
{
|
|
||||||
return 0UL;
|
public readonly ulong[]? Values => _block?.Cycles;
|
||||||
}
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
return Cycles[(int)(divisor - Start)];
|
public readonly ulong GetCycle(ulong divisor) => _block?.GetCycle(divisor) ?? 0UL;
|
||||||
}
|
|
||||||
}
|
public void Dispose()
|
||||||
|
{
|
||||||
public struct Lease : IDisposable
|
_block = null;
|
||||||
{
|
}
|
||||||
// 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 readonly object _sync = new();
|
||||||
|
private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new();
|
||||||
internal Lease(DivisorCycleCache owner, CycleBlock block)
|
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||||
{
|
private CycleBlock _baseBlock = null!;
|
||||||
_owner = owner;
|
private CycleBlock? _activeBlock;
|
||||||
_block = block;
|
private CycleBlock? _prefetchedBlock;
|
||||||
_block.AddRef();
|
private bool _useGpuGeneration = true;
|
||||||
}
|
|
||||||
|
public static DivisorCycleCache Shared { get; } = new DivisorCycleCache();
|
||||||
// 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;
|
private DivisorCycleCache()
|
||||||
|
{
|
||||||
// TODO: Once we make _block non-nullable we can simplify these to just fields assigned during the creation for the best performance.
|
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||||
public readonly ulong Start => _block?.Start ?? 0UL;
|
Initialize(snapshot);
|
||||||
|
}
|
||||||
public readonly ulong End => _block?.End ?? 0UL;
|
|
||||||
|
public void ReloadFromCurrentSnapshot()
|
||||||
public readonly ulong[]? Values => _block?.Cycles;
|
{
|
||||||
|
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
Initialize(snapshot);
|
||||||
public readonly ulong GetCycle(ulong divisor) => _block?.GetCycle(divisor) ?? 0UL;
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void ConfigureGeneratorDevice(bool useGpu)
|
||||||
{
|
{
|
||||||
// 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.
|
lock (_sync)
|
||||||
CycleBlock? block = _block;
|
{
|
||||||
if (block is null)
|
_useGpuGeneration = useGpu;
|
||||||
{
|
}
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
private void Initialize(ulong[] snapshot)
|
||||||
block.Release();
|
{
|
||||||
_owner?.Release(block);
|
lock (_sync)
|
||||||
_block = null;
|
{
|
||||||
}
|
_pending.Clear();
|
||||||
}
|
_baseBlock = new CycleBlock(index: 0, start: 0UL, snapshot);
|
||||||
|
_activeBlock = null;
|
||||||
private readonly object _sync = new();
|
_prefetchedBlock = null;
|
||||||
private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new();
|
StartPrefetchLocked(1);
|
||||||
// TODO: _baseBlock should be like no other block. Why do we need a separate property for it? It should become current upon initialization
|
}
|
||||||
private CycleBlock _baseBlock = null!;
|
}
|
||||||
private CycleBlock? _previous;
|
|
||||||
private CycleBlock? _current;
|
public Lease Acquire(ulong divisor)
|
||||||
private CycleBlock? _next;
|
{
|
||||||
private bool _initialized;
|
if (divisor <= _baseBlock.End)
|
||||||
|
{
|
||||||
// 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.
|
return new Lease(_baseBlock);
|
||||||
private static readonly Lazy<DivisorCycleCache> _lazy = new(() => new DivisorCycleCache());
|
}
|
||||||
|
|
||||||
public static DivisorCycleCache Shared => _lazy.Value;
|
CycleBlock block = AcquireDynamicBlock(divisor);
|
||||||
|
return new Lease(block);
|
||||||
private DivisorCycleCache()
|
}
|
||||||
{
|
|
||||||
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
private CycleBlock AcquireDynamicBlock(ulong divisor)
|
||||||
Initialize(snapshot);
|
{
|
||||||
}
|
int blockIndex = GetBlockIndex(divisor);
|
||||||
|
|
||||||
public void ReloadFromCurrentSnapshot()
|
while (true)
|
||||||
{
|
{
|
||||||
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
Task<CycleBlock>? pendingTask = null;
|
||||||
Initialize(snapshot);
|
lock (_sync)
|
||||||
}
|
{
|
||||||
|
if (TryGetCachedBlockLocked(blockIndex, out CycleBlock? cached))
|
||||||
private void Initialize(ulong[] snapshot)
|
{
|
||||||
{
|
return cached!;
|
||||||
lock (_sync)
|
}
|
||||||
{
|
|
||||||
_pending.Clear();
|
if (!_pending.TryGetValue(blockIndex, out pendingTask))
|
||||||
_baseBlock = new CycleBlock(index: 0, start: 0UL, snapshot);
|
{
|
||||||
_previous = _baseBlock;
|
pendingTask = Task.Run(() => GenerateBlock(blockIndex));
|
||||||
_current = null;
|
_pending[blockIndex] = pendingTask;
|
||||||
_next = null;
|
}
|
||||||
_initialized = true;
|
}
|
||||||
StartPrefetchLocked(1);
|
|
||||||
}
|
CycleBlock block = pendingTask.GetAwaiter().GetResult();
|
||||||
}
|
lock (_sync)
|
||||||
|
{
|
||||||
public Lease Acquire(ulong divisor)
|
_pending.TryRemove(blockIndex, out _);
|
||||||
{
|
|
||||||
// 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 (TryGetCachedBlockLocked(blockIndex, out CycleBlock? cachedAfterAwait))
|
||||||
if (!_initialized)
|
{
|
||||||
{
|
return cachedAfterAwait!;
|
||||||
throw new InvalidOperationException("DivisorCycleCache must be initialized before use.");
|
}
|
||||||
}
|
|
||||||
|
SetActiveBlockLocked(block);
|
||||||
// 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?
|
return block;
|
||||||
if (divisor <= _baseBlock.End)
|
}
|
||||||
{
|
}
|
||||||
return new Lease(this, _baseBlock);
|
}
|
||||||
}
|
|
||||||
|
private bool TryGetCachedBlockLocked(int blockIndex, out CycleBlock? block)
|
||||||
CycleBlock block = AcquireDynamicBlock(divisor);
|
{
|
||||||
return new Lease(this, block);
|
CycleBlock? active = _activeBlock;
|
||||||
}
|
if (active is not null && active.Index == blockIndex)
|
||||||
|
{
|
||||||
private CycleBlock AcquireDynamicBlock(ulong divisor)
|
block = active;
|
||||||
{
|
return true;
|
||||||
int blockIndex = GetBlockIndex(divisor);
|
}
|
||||||
while (true)
|
|
||||||
{
|
CycleBlock? prefetched = _prefetchedBlock;
|
||||||
Task<CycleBlock>? taskToAwait = null;
|
if (prefetched is not null && prefetched.Index == blockIndex)
|
||||||
lock (_sync)
|
{
|
||||||
{
|
PromotePrefetchedLocked();
|
||||||
if (TryGetCachedBlockLocked(blockIndex, out CycleBlock? cached))
|
block = _activeBlock!;
|
||||||
{
|
return true;
|
||||||
return cached!;
|
}
|
||||||
}
|
|
||||||
|
block = null;
|
||||||
if (!_pending.TryGetValue(blockIndex, out taskToAwait))
|
return false;
|
||||||
{
|
}
|
||||||
taskToAwait = Task.Run(() => GenerateBlock(blockIndex));
|
|
||||||
_pending[blockIndex] = taskToAwait;
|
private void PromotePrefetchedLocked()
|
||||||
}
|
{
|
||||||
}
|
CycleBlock? prefetched = _prefetchedBlock;
|
||||||
|
if (prefetched is null)
|
||||||
CycleBlock block = taskToAwait.GetAwaiter().GetResult();
|
{
|
||||||
lock (_sync)
|
return;
|
||||||
{
|
}
|
||||||
_pending.TryRemove(blockIndex, out _);
|
|
||||||
|
_activeBlock = prefetched;
|
||||||
if (TryGetCachedBlockLocked(blockIndex, out CycleBlock? cachedAfterAwait))
|
_prefetchedBlock = null;
|
||||||
{
|
StartPrefetchLocked(prefetched.Index + 1);
|
||||||
return cachedAfterAwait!;
|
}
|
||||||
}
|
|
||||||
|
private void SetActiveBlockLocked(CycleBlock block)
|
||||||
PromoteBlockLocked(block);
|
{
|
||||||
return block;
|
_activeBlock = block;
|
||||||
}
|
|
||||||
}
|
CycleBlock? prefetched = _prefetchedBlock;
|
||||||
}
|
if (prefetched is not null && prefetched.Index != block.Index + 1)
|
||||||
|
{
|
||||||
private bool TryGetCachedBlockLocked(int blockIndex, out CycleBlock? block)
|
_prefetchedBlock = null;
|
||||||
{
|
}
|
||||||
if (_current is { Index: var currentIndex } current && currentIndex == blockIndex)
|
|
||||||
{
|
StartPrefetchLocked(block.Index + 1);
|
||||||
block = current;
|
}
|
||||||
return true;
|
|
||||||
}
|
private void StartPrefetchLocked(int blockIndex)
|
||||||
|
{
|
||||||
if (_previous is { Index: var previousIndex } previous && previousIndex == blockIndex)
|
if (blockIndex <= 0)
|
||||||
{
|
{
|
||||||
block = previous;
|
return;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
CycleBlock? prefetched = _prefetchedBlock;
|
||||||
if (_next is { Index: var nextIndex } next && nextIndex == blockIndex)
|
if (prefetched is not null && prefetched.Index == blockIndex)
|
||||||
{
|
{
|
||||||
PromoteNextLocked();
|
return;
|
||||||
block = _current!;
|
}
|
||||||
return true;
|
|
||||||
}
|
if (_pending.TryGetValue(blockIndex, out Task<CycleBlock>? existing))
|
||||||
|
{
|
||||||
block = null;
|
if (existing.IsCompletedSuccessfully)
|
||||||
return false;
|
{
|
||||||
}
|
_pending.TryRemove(blockIndex, out _);
|
||||||
|
AssignPrefetchedBlockLocked(blockIndex, existing.Result);
|
||||||
private void PromoteNextLocked()
|
}
|
||||||
{
|
|
||||||
if (_next is null)
|
return;
|
||||||
{
|
}
|
||||||
return;
|
|
||||||
}
|
Task<CycleBlock> task = Task.Run(() => GenerateBlock(blockIndex));
|
||||||
|
_pending[blockIndex] = task;
|
||||||
CycleBlock? oldPrevious = _previous;
|
task.ContinueWith(t => OnPrefetchCompleted(blockIndex, t), TaskScheduler.Default);
|
||||||
_previous = _current ?? _baseBlock;
|
}
|
||||||
_current = _next;
|
|
||||||
_next = null;
|
private void OnPrefetchCompleted(int blockIndex, Task<CycleBlock> task)
|
||||||
StartPrefetchLocked(_current!.Index + 1);
|
{
|
||||||
TryReleaseBlockLocked(oldPrevious);
|
if (!task.IsCompletedSuccessfully)
|
||||||
}
|
{
|
||||||
|
_pending.TryRemove(blockIndex, out _);
|
||||||
private void PromoteBlockLocked(CycleBlock block)
|
return;
|
||||||
{
|
}
|
||||||
if (_current is null)
|
|
||||||
{
|
lock (_sync)
|
||||||
_previous = _baseBlock;
|
{
|
||||||
_current = block;
|
_pending.TryRemove(blockIndex, out _);
|
||||||
StartPrefetchLocked(block.Index + 1);
|
AssignPrefetchedBlockLocked(blockIndex, task.Result);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.Index > _current.Index)
|
private void AssignPrefetchedBlockLocked(int blockIndex, CycleBlock block)
|
||||||
{
|
{
|
||||||
CycleBlock? oldPrevious = _previous;
|
CycleBlock? active = _activeBlock;
|
||||||
_previous = _current;
|
if (active is not null && blockIndex != active.Index + 1)
|
||||||
_current = block;
|
{
|
||||||
StartPrefetchLocked(block.Index + 1);
|
return;
|
||||||
TryReleaseBlockLocked(oldPrevious);
|
}
|
||||||
return;
|
|
||||||
}
|
if (_prefetchedBlock is null || _prefetchedBlock.Index < blockIndex)
|
||||||
|
{
|
||||||
if (_previous is null || block.Index >= _previous.Index)
|
_prefetchedBlock = block;
|
||||||
{
|
}
|
||||||
CycleBlock? oldPrevious = _previous;
|
}
|
||||||
_previous = block;
|
|
||||||
TryReleaseBlockLocked(oldPrevious);
|
private CycleBlock GenerateBlock(int blockIndex)
|
||||||
}
|
{
|
||||||
}
|
if (blockIndex == 0)
|
||||||
|
{
|
||||||
private void StartPrefetchLocked(int blockIndex)
|
return _baseBlock;
|
||||||
{
|
}
|
||||||
// TODO: Why do we need this check? This should never happen in production code.
|
|
||||||
if (blockIndex <= 0)
|
ulong start = GetBlockStart(blockIndex);
|
||||||
{
|
int length = _baseBlock.Cycles.Length;
|
||||||
return;
|
ulong[] cycles = new ulong[length];
|
||||||
}
|
|
||||||
|
if (_useGpuGeneration)
|
||||||
// TODO: Why do we need this check? This should never happen in production code.
|
{
|
||||||
if (_next is { Index: var nextIndex } next && nextIndex == blockIndex)
|
ComputeCyclesGpu(start, cycles);
|
||||||
{
|
}
|
||||||
return;
|
else
|
||||||
}
|
{
|
||||||
|
ComputeCyclesCpu(start, cycles);
|
||||||
if (_pending.TryGetValue(blockIndex, out Task<CycleBlock>? existing))
|
}
|
||||||
{
|
|
||||||
if (existing.IsCompletedSuccessfully)
|
return new CycleBlock(blockIndex, start, cycles);
|
||||||
{
|
}
|
||||||
if (_next is null || _next.Index < blockIndex)
|
|
||||||
{
|
private int GetBlockIndex(ulong divisor)
|
||||||
_next = existing.Result;
|
{
|
||||||
}
|
if (divisor <= _baseBlock.End)
|
||||||
|
{
|
||||||
_pending.TryRemove(blockIndex, out _);
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
ulong offset = divisor - (_baseBlock.End + 1UL);
|
||||||
}
|
return (int)(offset / (ulong)PerfectNumberConstants.MaxQForDivisorCycles) + 1;
|
||||||
|
}
|
||||||
Task<CycleBlock> task = Task.Run(() => GenerateBlock(blockIndex));
|
|
||||||
_pending[blockIndex] = task;
|
private ulong GetBlockStart(int blockIndex)
|
||||||
task.ContinueWith(t => OnPrefetchCompleted(blockIndex, t), TaskScheduler.Default);
|
{
|
||||||
}
|
if (blockIndex == 0)
|
||||||
|
{
|
||||||
private void OnPrefetchCompleted(int blockIndex, Task<CycleBlock> task)
|
return 0UL;
|
||||||
{
|
}
|
||||||
if (!task.IsCompletedSuccessfully)
|
|
||||||
{
|
ulong baseEnd = _baseBlock.End;
|
||||||
_pending.TryRemove(blockIndex, out _);
|
return baseEnd + 1UL + (ulong)(blockIndex - 1) * (ulong)PerfectNumberConstants.MaxQForDivisorCycles;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
private void ComputeCyclesCpu(ulong start, ulong[] destination)
|
||||||
lock (_sync)
|
{
|
||||||
{
|
ulong divisor = start;
|
||||||
if (_next is null || _next.Index < blockIndex)
|
for (int i = 0; i < destination.Length; i++)
|
||||||
{
|
{
|
||||||
_next = task.Result;
|
destination[i] = MersenneDivisorCycles.CalculateCycleLength(divisor);
|
||||||
}
|
divisor++;
|
||||||
|
}
|
||||||
_pending.TryRemove(blockIndex, out _);
|
}
|
||||||
TrimCacheLocked();
|
|
||||||
}
|
private void ComputeCyclesGpu(ulong start, ulong[] destination)
|
||||||
}
|
{
|
||||||
|
var gpuLease = GpuKernelPool.GetKernel(_useGpuGeneration);
|
||||||
private void TrimCacheLocked()
|
var execution = gpuLease.EnterExecutionScope();
|
||||||
{
|
|
||||||
if (_previous is null)
|
try
|
||||||
{
|
{
|
||||||
return;
|
Accelerator accelerator = gpuLease.Accelerator;
|
||||||
}
|
Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel);
|
||||||
|
|
||||||
if (ReferenceEquals(_previous, _baseBlock))
|
int length = destination.Length;
|
||||||
{
|
ulong[] divisors = ArrayPool<ulong>.Shared.Rent(length);
|
||||||
return;
|
|
||||||
}
|
try
|
||||||
|
{
|
||||||
if (_previous.IsInUse)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
return;
|
divisors[i] = start + (ulong)i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_next is not null && _current is not null)
|
using MemoryBuffer1D<ulong, Stride1D.Dense> divisorBuffer = accelerator.Allocate1D<ulong>(length);
|
||||||
{
|
using MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = accelerator.Allocate1D<ulong>(length);
|
||||||
_previous = null;
|
|
||||||
}
|
divisorBuffer.View.CopyFromCPU(ref divisors[0], length);
|
||||||
}
|
kernel(length, divisorBuffer.View, resultBuffer.View);
|
||||||
|
accelerator.Synchronize();
|
||||||
private void TryReleaseBlockLocked(CycleBlock? block)
|
resultBuffer.View.CopyToCPU(ref destination[0], length);
|
||||||
{
|
}
|
||||||
if (block is null || ReferenceEquals(block, _baseBlock))
|
finally
|
||||||
{
|
{
|
||||||
return;
|
ArrayPool<ulong>.Shared.Return(divisors, clearArray: false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (ReferenceEquals(block, _current) || ReferenceEquals(block, _next))
|
finally
|
||||||
{
|
{
|
||||||
return;
|
execution.Dispose();
|
||||||
}
|
gpuLease.Dispose();
|
||||||
|
}
|
||||||
if (block.IsInUse)
|
}
|
||||||
{
|
|
||||||
return;
|
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);
|
||||||
if (ReferenceEquals(block, _previous))
|
}
|
||||||
{
|
|
||||||
_previous = null;
|
private static void GpuDivisorCycleKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> divisors, ArrayView1D<ulong, Stride1D.Dense> cycles)
|
||||||
}
|
{
|
||||||
}
|
cycles[index] = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[index]);
|
||||||
|
}
|
||||||
private void Release(CycleBlock block)
|
|
||||||
{
|
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
|
||||||
if (!block.IsInUse)
|
{
|
||||||
{
|
internal static AcceleratorReferenceComparer Instance { get; } = new();
|
||||||
lock (_sync)
|
|
||||||
{
|
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
|
||||||
TryReleaseBlockLocked(block);
|
|
||||||
}
|
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private CycleBlock GenerateBlock(int blockIndex)
|
|
||||||
{
|
|
||||||
if (blockIndex == 0)
|
|
||||||
{
|
|
||||||
return _baseBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong start = GetBlockStart(blockIndex);
|
|
||||||
int length = GetBlockLength(blockIndex);
|
|
||||||
ulong[] cycles = new ulong[length];
|
|
||||||
ulong divisor = start;
|
|
||||||
for (int i = 0; i < length; i++)
|
|
||||||
{
|
|
||||||
cycles[i] = MersenneDivisorCycles.CalculateCycleLength(divisor);
|
|
||||||
divisor++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CycleBlock(blockIndex, start, cycles);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GetBlockLength(int blockIndex)
|
|
||||||
{
|
|
||||||
// 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 (blockIndex == 0)
|
|
||||||
{
|
|
||||||
return _baseBlock.Cycles.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PerfectNumberConstants.MaxQForDivisorCycles;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return 0UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong baseEnd = _baseBlock.End;
|
|
||||||
return baseEnd + 1UL + (ulong)(blockIndex - 1) * (ulong)PerfectNumberConstants.MaxQForDivisorCycles;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GetBlockIndex(ulong divisor)
|
|
||||||
{
|
|
||||||
// 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 (divisor <= _baseBlock.End)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong offset = divisor - (_baseBlock.End + 1UL);
|
|
||||||
return (int)(offset / (ulong)PerfectNumberConstants.MaxQForDivisorCycles) + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -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