Merge pull request #90 from mlemanczyk/codex/optimize-memory-usage-in-evenperfectbitscanner
Optimize cycle remainder reuse in by-divisor testers
This commit is contained in:
10 files changed
+738
-331
No files matched your search
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using System.Numerics;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -13,8 +13,8 @@ public class Pow2MontgomeryModBenchmarks
|
||||
{
|
||||
private const int SampleCount = 256;
|
||||
|
||||
private readonly MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData[] _smallDivisors = new MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData[SampleCount];
|
||||
private readonly MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData[] _largeDivisors = new MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData[SampleCount];
|
||||
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly ulong[] _smallExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _largeExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _smallCycles = new ulong[SampleCount];
|
||||
@@ -184,7 +184,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private void GetData(out ulong[] exponents, out MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
||||
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
||||
{
|
||||
if (Scale == InputScale.Small)
|
||||
{
|
||||
@@ -217,14 +217,14 @@ public class Pow2MontgomeryModBenchmarks
|
||||
return (modulus, (ulong)bitLength);
|
||||
}
|
||||
|
||||
private static MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return new MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL);
|
||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL);
|
||||
}
|
||||
|
||||
return new MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData(
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
ComputeMontgomeryNPrime(modulus),
|
||||
ComputeMontgomeryResidue(1UL, modulus),
|
||||
@@ -245,7 +245,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData divisor)
|
||||
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
@@ -277,7 +277,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData divisor)
|
||||
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
@@ -326,7 +326,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData divisor)
|
||||
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
|
||||
@@ -899,21 +899,29 @@ internal static class Program
|
||||
divisor = AcquireNextDivisor(ref nextDivisor, divisorLimit, ref divisorsExhaustedFlag, ref finalDivisorBits, out exhausted, ref localDivisorCursor, ref localDivisorsRemaining);
|
||||
if (divisor != 0UL)
|
||||
{
|
||||
divisorCycle = useDivisorCycles
|
||||
? DivisorCycleCache.Shared.Acquire(divisor).GetCycle(divisor)
|
||||
: 0UL;
|
||||
DivisorCycleCache.CycleBlock? cycleBlock = null;
|
||||
if (useDivisorCycles)
|
||||
{
|
||||
cycleBlock = DivisorCycleCache.Shared.Acquire(divisor);
|
||||
divisorCycle = cycleBlock.GetCycle(divisor);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = 0UL;
|
||||
}
|
||||
|
||||
activeCount = BuildPrimeBuffer(divisor, primeValues, allowedMaxValues, stateFlags, primeBuffer, indexBuffer, completionsBuffer, ref completionsCount, ref remainingStates, activeStateMask, ref activeStartIndex);
|
||||
activeCount = BuildPrimeBuffer(divisor, primeValues, allowedMaxValues, stateFlags, primeBuffer, indexBuffer, completionsBuffer, ref completionsCount, ref remainingStates, activeStateMask, ref activeStartIndex);
|
||||
|
||||
if (completionsCount > 0)
|
||||
{
|
||||
FlushPendingResults(completionsBuffer, ref completionsCount);
|
||||
}
|
||||
if (completionsCount > 0)
|
||||
{
|
||||
FlushPendingResults(completionsBuffer, ref completionsCount);
|
||||
}
|
||||
|
||||
if (activeCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (activeCount == 0)
|
||||
{
|
||||
cycleBlock?.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
hitsSpan = hitsBuffer.AsSpan(0, activeCount);
|
||||
hitsSpan.Clear();
|
||||
@@ -940,10 +948,12 @@ internal static class Program
|
||||
}
|
||||
}
|
||||
|
||||
if (compositesCount > 0)
|
||||
{
|
||||
FlushPendingResults(compositesBuffer, ref compositesCount);
|
||||
}
|
||||
if (compositesCount > 0)
|
||||
{
|
||||
FlushPendingResults(compositesBuffer, ref compositesCount);
|
||||
}
|
||||
|
||||
cycleBlock?.Dispose();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
@@ -37,7 +38,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
typeof(MersenneNumberDivisorGpuTester)
|
||||
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
|
||||
.SetValue(null, Array.Empty<(ulong, ulong)>());
|
||||
.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||
|
||||
tester.IsPrime(11UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeFalse();
|
||||
@@ -102,6 +103,22 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
UseDivisorCycles = true,
|
||||
GpuBatchSize = 8,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(13UL);
|
||||
|
||||
tester.IsPrime(11UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_checks_divisors_across_primes()
|
||||
@@ -122,5 +139,49 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
session.CheckDivisor(31UL, cycle31, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_session_reuses_cycle_remainders_across_batches()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
UseDivisorCycles = true,
|
||||
GpuBatchSize = 2,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(17UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL, 17UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_cpu_session_reuses_cycle_remainders_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester
|
||||
{
|
||||
UseDivisorCycles = true,
|
||||
BatchSize = 2,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(17UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL, 17UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using MontgomeryDivisorData = PerfectNumbers.Core.Gpu.MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
@@ -167,12 +165,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
processedCount++;
|
||||
lastProcessed = divisor;
|
||||
|
||||
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
byte hit;
|
||||
ulong divisorCycle = 0UL;
|
||||
DivisorCycleCache.CycleBlock? cycleBlock = null;
|
||||
if (useCycles)
|
||||
{
|
||||
DivisorCycleCache.CycleBlock cycleBlock = DivisorCycleCache.Shared.Acquire(divisor);
|
||||
ulong divisorCycle = cycleBlock.GetCycle(divisor);
|
||||
cycleBlock = DivisorCycleCache.Shared.Acquire(divisor);
|
||||
divisorCycle = cycleBlock.GetCycle(divisor);
|
||||
hit = CheckDivisor(prime, divisorCycle != 0UL, divisorCycle, divisorData);
|
||||
}
|
||||
else
|
||||
@@ -180,6 +180,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
hit = CheckDivisor(prime, false, 0UL, divisorData);
|
||||
}
|
||||
|
||||
cycleBlock?.Dispose();
|
||||
|
||||
if (hit != 0)
|
||||
{
|
||||
processedAll = true;
|
||||
@@ -270,35 +272,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||
}
|
||||
|
||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL);
|
||||
}
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
ComputeMontgomeryNPrime(modulus),
|
||||
ComputeMontgomeryResidue(1UL, modulus),
|
||||
ComputeMontgomeryResidue(2UL, modulus));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
||||
|
||||
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||||
{
|
||||
ulong inv = modulus;
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
|
||||
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
|
||||
@@ -335,26 +308,31 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
}
|
||||
|
||||
bool cycleEnabled = _owner._useDivisorCycles && divisorCycle != 0UL;
|
||||
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
if (cycleEnabled)
|
||||
{
|
||||
ulong prime = primes[i];
|
||||
var stepper = new CycleRemainderStepper(divisorCycle);
|
||||
|
||||
if (cycleEnabled)
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong remainder = prime % divisorCycle;
|
||||
ulong remainder = stepper.ComputeNext(primes[i]);
|
||||
if (remainder != 0UL)
|
||||
{
|
||||
hits[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong residue = remainder.Pow2MontgomeryModFromCycleRemainder(divisorData);
|
||||
hits[i] = residue == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
ulong residue = cycleEnabled
|
||||
? prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData)
|
||||
: prime.Pow2MontgomeryMod(divisorData);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong residue = primes[i].Pow2MontgomeryMod(divisorData);
|
||||
hits[i] = residue == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
internal struct CycleRemainderStepper
|
||||
{
|
||||
private readonly ulong _cycleLength;
|
||||
private ulong _previousPrime;
|
||||
private ulong _currentRemainder;
|
||||
private bool _hasState;
|
||||
|
||||
public CycleRemainderStepper(ulong cycleLength)
|
||||
{
|
||||
_cycleLength = cycleLength;
|
||||
_previousPrime = 0UL;
|
||||
_currentRemainder = 0UL;
|
||||
_hasState = false;
|
||||
}
|
||||
|
||||
public bool HasCycle => _cycleLength != 0UL;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_previousPrime = 0UL;
|
||||
_currentRemainder = 0UL;
|
||||
_hasState = false;
|
||||
}
|
||||
|
||||
public void InitializeState(ulong previousPrime, ulong previousRemainder, bool hasState)
|
||||
{
|
||||
_previousPrime = previousPrime;
|
||||
_currentRemainder = previousRemainder;
|
||||
_hasState = hasState;
|
||||
}
|
||||
|
||||
public ulong ComputeNext(ulong prime)
|
||||
{
|
||||
if (_cycleLength == 0UL)
|
||||
{
|
||||
_previousPrime = prime;
|
||||
_currentRemainder = 0UL;
|
||||
_hasState = true;
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
if (!_hasState || prime <= _previousPrime)
|
||||
{
|
||||
_currentRemainder = prime % _cycleLength;
|
||||
_hasState = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong delta = prime - _previousPrime;
|
||||
if (ulong.MaxValue - _currentRemainder < delta)
|
||||
{
|
||||
UInt128 extended = (UInt128)_currentRemainder + delta;
|
||||
_currentRemainder = (ulong)(extended % _cycleLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentRemainder += delta;
|
||||
|
||||
if (_currentRemainder >= _cycleLength)
|
||||
{
|
||||
_currentRemainder -= _cycleLength;
|
||||
if (_currentRemainder >= _cycleLength)
|
||||
{
|
||||
_currentRemainder %= _cycleLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_previousPrime = prime;
|
||||
return _currentRemainder;
|
||||
}
|
||||
|
||||
public void CaptureState(out ulong previousPrime, out ulong previousRemainder, out bool hasState)
|
||||
{
|
||||
previousPrime = _previousPrime;
|
||||
previousRemainder = _currentRemainder;
|
||||
hasState = _hasState;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
@@ -12,16 +13,52 @@ namespace PerfectNumbers.Core;
|
||||
|
||||
public sealed class DivisorCycleCache
|
||||
{
|
||||
public sealed class CycleBlock(int index, ulong start, ulong[] cycles)
|
||||
{
|
||||
internal readonly int Index = index;
|
||||
internal readonly ulong Start = start;
|
||||
internal readonly ulong End = start + (ulong)cycles.Length - 1UL;
|
||||
internal readonly ulong[] Cycles = cycles;
|
||||
public sealed class CycleBlock : IDisposable
|
||||
{
|
||||
private readonly DivisorCycleCache _owner;
|
||||
private int _referenceCount;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong GetCycle(ulong divisor) => Cycles[(int)(divisor - Start)];
|
||||
}
|
||||
internal CycleBlock(DivisorCycleCache owner, int index, ulong start, in ulong[] cycles)
|
||||
{
|
||||
_owner = owner;
|
||||
Index = index;
|
||||
Start = start;
|
||||
End = start + (ulong)cycles.Length - 1UL;
|
||||
Cycles = cycles;
|
||||
}
|
||||
|
||||
internal int Index { get; }
|
||||
|
||||
internal ulong Start { get; }
|
||||
|
||||
internal ulong End { get; }
|
||||
|
||||
internal ulong[] Cycles { get; }
|
||||
|
||||
internal int ReferenceCount => Volatile.Read(ref _referenceCount);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong GetCycle(ulong divisor) => Cycles[(int)(divisor - Start)];
|
||||
|
||||
internal void Retain()
|
||||
{
|
||||
Interlocked.Increment(ref _referenceCount);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
int remaining = Interlocked.Decrement(ref _referenceCount);
|
||||
if (remaining < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cycle block released more times than it was acquired.");
|
||||
}
|
||||
|
||||
if (remaining == 0)
|
||||
{
|
||||
_owner.OnCycleBlockReleased(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const int CycleGenerationBatchSize = 262_144;
|
||||
private const byte ByteZero = 0;
|
||||
@@ -63,24 +100,37 @@ public sealed class DivisorCycleCache
|
||||
|
||||
private void Initialize(ulong[] snapshot)
|
||||
{
|
||||
CycleBlock? activeToDispose = null;
|
||||
CycleBlock? prefetchedToDispose = null;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_activeBlock is not null && _activeBlock.Index != 0)
|
||||
{
|
||||
activeToDispose = _activeBlock;
|
||||
}
|
||||
|
||||
if (_prefetchedBlock is not null && _prefetchedBlock.Index != 0)
|
||||
{
|
||||
prefetchedToDispose = _prefetchedBlock;
|
||||
}
|
||||
|
||||
_pending.Clear();
|
||||
_baseBlock = new CycleBlock(index: 0, start: 0UL, snapshot);
|
||||
_baseBlock = new CycleBlock(this, index: 0, start: 0UL, snapshot);
|
||||
_activeBlock = null;
|
||||
_prefetchedBlock = null;
|
||||
StartPrefetchLocked(1);
|
||||
}
|
||||
|
||||
activeToDispose?.Dispose();
|
||||
prefetchedToDispose?.Dispose();
|
||||
}
|
||||
|
||||
public CycleBlock Acquire(ulong divisor)
|
||||
{
|
||||
if (divisor <= _baseBlock.End)
|
||||
{
|
||||
return _baseBlock;
|
||||
}
|
||||
|
||||
return AcquireDynamicBlock(divisor);
|
||||
CycleBlock block = divisor <= _baseBlock.End ? _baseBlock : AcquireDynamicBlock(divisor);
|
||||
block.Retain();
|
||||
return block;
|
||||
}
|
||||
|
||||
private CycleBlock AcquireDynamicBlock(ulong divisor)
|
||||
@@ -149,21 +199,42 @@ public sealed class DivisorCycleCache
|
||||
return;
|
||||
}
|
||||
|
||||
CycleBlock? previousActive = _activeBlock;
|
||||
_activeBlock = prefetched;
|
||||
_prefetchedBlock = null;
|
||||
prefetched.Retain();
|
||||
prefetched.Dispose();
|
||||
|
||||
if (previousActive is not null && previousActive.Index != 0 && previousActive.Index != prefetched.Index)
|
||||
{
|
||||
previousActive.Dispose();
|
||||
}
|
||||
|
||||
StartPrefetchLocked(prefetched.Index + 1);
|
||||
}
|
||||
|
||||
private void SetActiveBlockLocked(CycleBlock block)
|
||||
{
|
||||
CycleBlock? previousActive = _activeBlock;
|
||||
_activeBlock = block;
|
||||
block.Retain();
|
||||
|
||||
CycleBlock? prefetched = _prefetchedBlock;
|
||||
if (prefetched is not null && prefetched.Index != block.Index + 1)
|
||||
{
|
||||
if (prefetched.Index != 0)
|
||||
{
|
||||
prefetched.Dispose();
|
||||
}
|
||||
|
||||
_prefetchedBlock = null;
|
||||
}
|
||||
|
||||
if (previousActive is not null && previousActive.Index != 0 && previousActive.Index != block.Index)
|
||||
{
|
||||
previousActive.Dispose();
|
||||
}
|
||||
|
||||
StartPrefetchLocked(block.Index + 1);
|
||||
}
|
||||
|
||||
@@ -216,12 +287,59 @@ public sealed class DivisorCycleCache
|
||||
CycleBlock? active = _activeBlock;
|
||||
if (active is not null && blockIndex != active.Index + 1)
|
||||
{
|
||||
MontgomeryDivisorDataCache.ReleaseBlock(block);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_prefetchedBlock is null || _prefetchedBlock.Index < blockIndex)
|
||||
CycleBlock? previousPrefetched = _prefetchedBlock;
|
||||
if (ReferenceEquals(previousPrefetched, block))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_prefetchedBlock is null || _prefetchedBlock.Index <= blockIndex)
|
||||
{
|
||||
if (previousPrefetched is not null && previousPrefetched.Index != 0)
|
||||
{
|
||||
previousPrefetched.Dispose();
|
||||
}
|
||||
|
||||
_prefetchedBlock = block;
|
||||
block.Retain();
|
||||
}
|
||||
else
|
||||
{
|
||||
MontgomeryDivisorDataCache.ReleaseBlock(block);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCycleBlockReleased(CycleBlock block)
|
||||
{
|
||||
if (block.Index == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int attempt = 0; attempt < 8; attempt++)
|
||||
{
|
||||
if (block.ReferenceCount != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Thread.SpinWait(1 << attempt);
|
||||
}
|
||||
|
||||
bool stillTracked;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
stillTracked = ReferenceEquals(_activeBlock, block) || ReferenceEquals(_prefetchedBlock, block);
|
||||
}
|
||||
|
||||
if (!stillTracked && block.ReferenceCount == 0)
|
||||
{
|
||||
MontgomeryDivisorDataCache.ReleaseBlock(block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +363,7 @@ public sealed class DivisorCycleCache
|
||||
ComputeCyclesCpu(start, cycles);
|
||||
}
|
||||
|
||||
return new CycleBlock(blockIndex, start, cycles);
|
||||
return new CycleBlock(this, blockIndex, start, cycles);
|
||||
}
|
||||
|
||||
private int GetBlockIndex(ulong divisor)
|
||||
|
||||
@@ -2,36 +2,33 @@ using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Numerics;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using MontgomeryDivisorData = PerfectNumbers.Core.MontgomeryDivisorData;
|
||||
using MontgomeryDivisorDataCache = PerfectNumbers.Core.MontgomeryDivisorDataCache;
|
||||
using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
||||
private readonly object _sync = new();
|
||||
private ulong _divisorLimit;
|
||||
private bool _isConfigured;
|
||||
private bool _useDivisorCycles;
|
||||
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
||||
private readonly object _sync = new();
|
||||
private ulong _divisorLimit;
|
||||
private bool _isConfigured;
|
||||
private bool _useDivisorCycles;
|
||||
|
||||
public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong montgomeryOne, ulong montgomeryTwo)
|
||||
{
|
||||
public readonly ulong Modulus = modulus;
|
||||
public readonly ulong NPrime = nPrime;
|
||||
public readonly ulong MontgomeryOne = montgomeryOne;
|
||||
public readonly ulong MontgomeryTwo = montgomeryTwo;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, byte, ulong, ArrayView<MontgomeryDivisorData>, ArrayView<byte>>> _kernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>>> _kernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>> _kernelExponentCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
||||
|
||||
private Action<Index1D, ulong, byte, ulong, ArrayView<MontgomeryDivisorData>, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
|
||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, byte, ulong, ArrayView<MontgomeryDivisorData>, ArrayView<byte>>(CheckKernel));
|
||||
private Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> GetKernelByPrimeExponent(Accelerator accelerator) =>
|
||||
_kernelExponentCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputePrimeExponentKernel));
|
||||
private Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
|
||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>>(CheckKernel));
|
||||
|
||||
private Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> GetKernelByPrimeExponent(Accelerator accelerator) =>
|
||||
_kernelExponentCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputePrimeExponentKernel));
|
||||
|
||||
public int GpuBatchSize
|
||||
{
|
||||
@@ -96,21 +93,23 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
try
|
||||
{
|
||||
composite = CheckDivisors(
|
||||
prime,
|
||||
allowedMax,
|
||||
useCycles,
|
||||
accelerator,
|
||||
kernel,
|
||||
resources.DivisorsBuffer,
|
||||
resources.HitsBuffer,
|
||||
resources.Divisors,
|
||||
resources.Hits,
|
||||
resources.DivisorData,
|
||||
out lastProcessed,
|
||||
out coveredRange,
|
||||
out processedCount
|
||||
);
|
||||
composite = CheckDivisors(
|
||||
prime,
|
||||
allowedMax,
|
||||
useCycles,
|
||||
accelerator,
|
||||
kernel,
|
||||
resources.DivisorsBuffer,
|
||||
resources.ExponentBuffer,
|
||||
resources.HitsBuffer,
|
||||
resources.Divisors,
|
||||
resources.Exponents,
|
||||
resources.Hits,
|
||||
resources.DivisorData,
|
||||
out lastProcessed,
|
||||
out coveredRange,
|
||||
out processedCount
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -128,17 +127,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CheckDivisors(
|
||||
ulong prime,
|
||||
ulong allowedMax,
|
||||
bool useCycles,
|
||||
Accelerator accelerator,
|
||||
Action<Index1D, ulong, byte, ulong, ArrayView<MontgomeryDivisorData>, ArrayView<byte>> kernel,
|
||||
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
|
||||
MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer,
|
||||
in ulong[] divisors,
|
||||
byte[] hits,
|
||||
MontgomeryDivisorData[] divisorData,
|
||||
private static bool CheckDivisors(
|
||||
ulong prime,
|
||||
ulong allowedMax,
|
||||
bool useCycles,
|
||||
Accelerator accelerator,
|
||||
Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> kernel,
|
||||
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer,
|
||||
MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer,
|
||||
in ulong[] divisors,
|
||||
ulong[] exponents,
|
||||
byte[] hits,
|
||||
MontgomeryDivisorData[] divisorData,
|
||||
out ulong lastProcessed,
|
||||
out bool coveredRange,
|
||||
out ulong processedCount)
|
||||
@@ -154,20 +155,22 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
int batchCapacity = (int)divisorsBuffer.Length;
|
||||
|
||||
bool composite = false;
|
||||
bool processedAll = false;
|
||||
ulong currentDivisor = 3UL, cycle = 0UL, next, nextDivisor;
|
||||
int batchSize, i;
|
||||
bool reachedEndInBatch;
|
||||
bool composite = false;
|
||||
bool processedAll = false;
|
||||
ulong currentDivisor = 3UL, next, nextDivisor;
|
||||
int batchSize, i;
|
||||
bool reachedEndInBatch;
|
||||
|
||||
Span<MontgomeryDivisorData> divisorDataSpan;
|
||||
Span<ulong> divisorSpan;
|
||||
Span<byte> hitsSpan;
|
||||
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> divisorView;
|
||||
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
||||
DivisorCycleCache divisorCyclesCache = DivisorCycleCache.Shared;
|
||||
DivisorCycleCache.CycleBlock? cycleLease = useCycles ? divisorCyclesCache.Acquire(currentDivisor) : null;
|
||||
ulong cycleLeaseEnd = cycleLease?.End ?? 0UL;
|
||||
Span<MontgomeryDivisorData> divisorDataSpan;
|
||||
Span<ulong> divisorSpan;
|
||||
Span<ulong> exponentSpan;
|
||||
Span<byte> hitsSpan;
|
||||
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> divisorView;
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
||||
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
||||
DivisorCycleCache divisorCyclesCache = DivisorCycleCache.Shared;
|
||||
DivisorCycleCache.CycleBlock? cycleLease = useCycles ? divisorCyclesCache.Acquire(currentDivisor) : null;
|
||||
ulong cycleLeaseEnd = cycleLease?.End ?? 0UL;
|
||||
|
||||
while (currentDivisor <= allowedMax)
|
||||
{
|
||||
@@ -179,26 +182,39 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
nextDivisor = currentDivisor + 2UL;
|
||||
processedCount++;
|
||||
|
||||
bool includeDivisor = true;
|
||||
if (useCycles)
|
||||
{
|
||||
if (currentDivisor > cycleLeaseEnd)
|
||||
{
|
||||
cycleLease = divisorCyclesCache.Acquire(currentDivisor);
|
||||
cycleLeaseEnd = cycleLease.End;
|
||||
}
|
||||
bool includeDivisor = true;
|
||||
ulong exponent = prime;
|
||||
|
||||
cycle = prime % cycleLease!.GetCycle(currentDivisor);
|
||||
if (cycle != 0UL)
|
||||
{
|
||||
includeDivisor = false;
|
||||
}
|
||||
}
|
||||
if (useCycles)
|
||||
{
|
||||
if (currentDivisor > cycleLeaseEnd)
|
||||
{
|
||||
cycleLease?.Dispose();
|
||||
cycleLease = divisorCyclesCache.Acquire(currentDivisor);
|
||||
cycleLeaseEnd = cycleLease.End;
|
||||
}
|
||||
|
||||
if (includeDivisor)
|
||||
{
|
||||
divisors[batchSize++] = currentDivisor;
|
||||
}
|
||||
ulong cycleLength = cycleLease!.GetCycle(currentDivisor);
|
||||
if (cycleLength > 0UL)
|
||||
{
|
||||
ulong remainder = prime % cycleLength;
|
||||
if (remainder != 0UL)
|
||||
{
|
||||
includeDivisor = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
exponent = remainder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeDivisor)
|
||||
{
|
||||
divisors[batchSize] = currentDivisor;
|
||||
exponents[batchSize] = exponent;
|
||||
batchSize++;
|
||||
}
|
||||
|
||||
lastProcessed = currentDivisor;
|
||||
next = nextDivisor;
|
||||
@@ -219,32 +235,34 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
currentDivisor = next;
|
||||
}
|
||||
|
||||
if (batchSize == 0)
|
||||
{
|
||||
if (reachedEndInBatch)
|
||||
{
|
||||
processedAll = true;
|
||||
break;
|
||||
}
|
||||
if (batchSize == 0)
|
||||
{
|
||||
if (reachedEndInBatch)
|
||||
{
|
||||
processedAll = true;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
||||
divisorView = divisorsBuffer.View.SubView(0, batchSize);
|
||||
hitsView = hitsBuffer.View.SubView(0, batchSize);
|
||||
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
||||
divisorView = divisorsBuffer.View.SubView(0, batchSize);
|
||||
exponentView = exponentBuffer.View.SubView(0, batchSize);
|
||||
hitsView = hitsBuffer.View.SubView(0, batchSize);
|
||||
|
||||
divisorSpan = divisors.AsSpan(0, batchSize);
|
||||
hitsSpan = hits.AsSpan(0, batchSize);
|
||||
divisorSpan = divisors.AsSpan(0, batchSize);
|
||||
exponentSpan = exponents.AsSpan(0, batchSize);
|
||||
hitsSpan = hits.AsSpan(0, batchSize);
|
||||
|
||||
for (i = 0; i < batchSize; i++)
|
||||
{
|
||||
divisorDataSpan[i] = CreateMontgomeryDivisorData(divisorSpan[i]);
|
||||
}
|
||||
for (i = 0; i < batchSize; i++)
|
||||
{
|
||||
divisorDataSpan[i] = MontgomeryDivisorDataCache.Get(divisorSpan[i]);
|
||||
}
|
||||
|
||||
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
||||
byte useCycleChecks = useCycles ? (byte)1 : (byte)0;
|
||||
kernel(batchSize, prime, useCycleChecks, cycle, divisorView, hitsView);
|
||||
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), batchSize);
|
||||
kernel(batchSize, divisorView, exponentView, hitsView);
|
||||
accelerator.Synchronize();
|
||||
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize);
|
||||
|
||||
@@ -274,9 +292,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
}
|
||||
}
|
||||
|
||||
coveredRange = composite || processedAll || currentDivisor > allowedMax;
|
||||
cycleLease?.Dispose();
|
||||
coveredRange = composite || processedAll || currentDivisor > allowedMax;
|
||||
|
||||
return composite;
|
||||
return composite;
|
||||
}
|
||||
|
||||
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
||||
@@ -309,23 +328,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||
}
|
||||
|
||||
private static void CheckKernel(Index1D index, ulong prime, byte useCycleChecks, ulong cycle, ArrayView<MontgomeryDivisorData> divisors, ArrayView<byte> hits)
|
||||
{
|
||||
MontgomeryDivisorData divisor = divisors[index];
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
hits[index] = 0;
|
||||
return;
|
||||
}
|
||||
private static void CheckKernel(Index1D index, ArrayView<MontgomeryDivisorData> divisors, ArrayView<ulong> exponents, ArrayView<byte> hits)
|
||||
{
|
||||
MontgomeryDivisorData divisor = divisors[index];
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
hits[index] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (useCycleChecks != 0)
|
||||
{
|
||||
prime = cycle;
|
||||
}
|
||||
|
||||
hits[index] = prime.Pow2MontgomeryMod(divisor) == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
ulong exponent = exponents[index];
|
||||
hits[index] = exponent.Pow2MontgomeryMod(divisor) == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
@@ -364,7 +379,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
int gpuBatchSize = owner._gpuBatchSize,
|
||||
primesLength = primes.Length;
|
||||
|
||||
MontgomeryDivisorData divisorData = CreateMontgomeryDivisorData(divisor);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
ulong modulus = divisorData.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
@@ -376,52 +391,60 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentView, exponentsView = _exponentsBuffer.View;
|
||||
ArrayView1D<ulong, Stride1D.Dense> resultView, resultsView = _resultsBuffer.View;
|
||||
|
||||
Span<ulong> exponentSlice, hostSpan = new(_hostBuffer);
|
||||
Span<int> positionSpan = new(_positionBuffer);
|
||||
Span<byte> hitsSlice;
|
||||
ReadOnlySpan<ulong> primesSlice;
|
||||
Span<ulong> exponentSlice, hostSpan = new(_hostBuffer);
|
||||
Span<int> positionSpan = new(_positionBuffer);
|
||||
Span<byte> hitsSlice;
|
||||
ReadOnlySpan<ulong> primesSlice;
|
||||
|
||||
int batchSize, computeCount, i, offset = 0;
|
||||
bool useDivisorCycles = owner._useDivisorCycles;
|
||||
ulong residue;
|
||||
while (offset < primesLength)
|
||||
{
|
||||
batchSize = Math.Min(gpuBatchSize, primesLength - offset);
|
||||
primesSlice = primes.Slice(offset, batchSize);
|
||||
hitsSlice = hits.Slice(offset, batchSize);
|
||||
int batchSize, computeCount, i, offset = 0;
|
||||
bool useDivisorCycles = owner._useDivisorCycles && divisorCycle != 0UL;
|
||||
CycleRemainderStepper cycleStepper = default;
|
||||
if (useDivisorCycles)
|
||||
{
|
||||
cycleStepper = new CycleRemainderStepper(divisorCycle);
|
||||
}
|
||||
while (offset < primesLength)
|
||||
{
|
||||
batchSize = Math.Min(gpuBatchSize, primesLength - offset);
|
||||
primesSlice = primes.Slice(offset, batchSize);
|
||||
hitsSlice = hits.Slice(offset, batchSize);
|
||||
|
||||
computeCount = 0;
|
||||
computeCount = 0;
|
||||
|
||||
if (useDivisorCycles)
|
||||
{
|
||||
for (i = 0; i < batchSize; i++)
|
||||
{
|
||||
residue = primesSlice[i] % divisorCycle;
|
||||
if (residue != 0UL)
|
||||
{
|
||||
// We don't need to clear hitsSlice[i] because the parent function calls .Clear() on the buffer
|
||||
continue;
|
||||
}
|
||||
if (useDivisorCycles)
|
||||
{
|
||||
Span<ulong> remainderSpan = hostSpan.Slice(0, batchSize);
|
||||
|
||||
positionSpan[computeCount] = i;
|
||||
hostSpan[computeCount] = residue;
|
||||
computeCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < batchSize; i++)
|
||||
{
|
||||
positionSpan[computeCount] = i;
|
||||
hostSpan[computeCount] = primesSlice[i];
|
||||
for (i = 0; i < batchSize; i++)
|
||||
{
|
||||
ulong remainder = cycleStepper.ComputeNext(primesSlice[i]);
|
||||
remainderSpan[i] = remainder;
|
||||
hitsSlice[i] = 0;
|
||||
|
||||
if (remainder != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
positionSpan[computeCount] = i;
|
||||
hostSpan[computeCount] = remainder;
|
||||
computeCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < batchSize; i++)
|
||||
{
|
||||
positionSpan[computeCount] = i;
|
||||
hostSpan[computeCount] = primesSlice[i];
|
||||
computeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (computeCount > 0)
|
||||
{
|
||||
exponentSlice = hostSpan[..computeCount];
|
||||
exponentView = exponentsView.SubView(0, computeCount);
|
||||
if (computeCount > 0)
|
||||
{
|
||||
exponentSlice = hostSpan[..computeCount];
|
||||
exponentView = exponentsView.SubView(0, computeCount);
|
||||
|
||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
||||
resultView = resultsView.SubView(0, computeCount);
|
||||
@@ -429,14 +452,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
accelerator.Synchronize();
|
||||
resultView.CopyToCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
||||
|
||||
for (i = 0; i < computeCount; i++)
|
||||
{
|
||||
hitsSlice[positionSpan[i]] = exponentSlice[i] == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < computeCount; i++)
|
||||
{
|
||||
hitsSlice[positionSpan[i]] = exponentSlice[i] == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
}
|
||||
|
||||
offset += batchSize;
|
||||
}
|
||||
offset += batchSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -463,35 +486,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
results[index] = exponents[index].Pow2MontgomeryMod(divisor);
|
||||
}
|
||||
|
||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return new(modulus, 0UL, 0UL, 0UL);
|
||||
}
|
||||
|
||||
return new(
|
||||
modulus,
|
||||
ComputeMontgomeryNPrime(modulus),
|
||||
ComputeMontgomeryResidue(1UL, modulus),
|
||||
ComputeMontgomeryResidue(2UL, modulus)
|
||||
);
|
||||
}
|
||||
|
||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
||||
|
||||
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||||
{
|
||||
ulong inv = modulus;
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
|
||||
public ulong DivisorLimit
|
||||
{
|
||||
get
|
||||
@@ -523,37 +517,45 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
private sealed class BatchResources : IDisposable
|
||||
{
|
||||
internal BatchResources(Accelerator accelerator, int capacity)
|
||||
{
|
||||
DivisorsBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(capacity);
|
||||
HitsBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||
Divisors = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||
Hits = ArrayPool<byte>.Shared.Rent(capacity);
|
||||
DivisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(capacity);
|
||||
Capacity = capacity;
|
||||
}
|
||||
internal BatchResources(Accelerator accelerator, int capacity)
|
||||
{
|
||||
DivisorsBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(capacity);
|
||||
ExponentBuffer = accelerator.Allocate1D<ulong>(capacity);
|
||||
HitsBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||
Divisors = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||
Exponents = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||
Hits = ArrayPool<byte>.Shared.Rent(capacity);
|
||||
DivisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(capacity);
|
||||
Capacity = capacity;
|
||||
}
|
||||
|
||||
internal readonly MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> DivisorsBuffer;
|
||||
internal readonly MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> DivisorsBuffer;
|
||||
|
||||
internal readonly MemoryBuffer1D<byte, Stride1D.Dense> HitsBuffer;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> ExponentBuffer;
|
||||
|
||||
internal readonly ulong[] Divisors;
|
||||
internal readonly MemoryBuffer1D<byte, Stride1D.Dense> HitsBuffer;
|
||||
|
||||
internal readonly byte[] Hits;
|
||||
internal readonly ulong[] Divisors;
|
||||
|
||||
internal readonly MontgomeryDivisorData[] DivisorData;
|
||||
internal readonly ulong[] Exponents;
|
||||
|
||||
internal readonly int Capacity;
|
||||
internal readonly byte[] Hits;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DivisorsBuffer.Dispose();
|
||||
HitsBuffer.Dispose();
|
||||
ArrayPool<ulong>.Shared.Return(Divisors, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(Hits, clearArray: false);
|
||||
ArrayPool<MontgomeryDivisorData>.Shared.Return(DivisorData, clearArray: false);
|
||||
}
|
||||
}
|
||||
internal readonly MontgomeryDivisorData[] DivisorData;
|
||||
|
||||
internal readonly int Capacity;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DivisorsBuffer.Dispose();
|
||||
ExponentBuffer.Dispose();
|
||||
HitsBuffer.Dispose();
|
||||
ArrayPool<ulong>.Shared.Return(Divisors, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(Exponents, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(Hits, clearArray: false);
|
||||
ArrayPool<MontgomeryDivisorData>.Shared.Return(DivisorData, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
|
||||
{
|
||||
|
||||
@@ -13,25 +13,25 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
private Action<Index1D, ulong, GpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
|
||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<byte>>(Kernel));
|
||||
|
||||
public static void BuildDivisorCandidates()
|
||||
{
|
||||
public static void BuildDivisorCandidates()
|
||||
{
|
||||
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
(ulong divisor, ulong cycle)[] list = new (ulong divisor, ulong cycle)[snapshot.Length / 2];
|
||||
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2];
|
||||
ulong cycle;
|
||||
int count = 0, i, snapshotLength = snapshot.Length;
|
||||
for (i = 3; i < snapshotLength; i += 2)
|
||||
{
|
||||
cycle = snapshot[i];
|
||||
if (cycle == 0U)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int count = 0, i, snapshotLength = snapshot.Length;
|
||||
for (i = 3; i < snapshotLength; i += 2)
|
||||
{
|
||||
cycle = snapshot[i];
|
||||
if (cycle == 0U)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list[count++] = ((ulong)i, cycle);
|
||||
}
|
||||
list[count++] = ((ulong)i, (uint)cycle);
|
||||
}
|
||||
|
||||
_divisorCandidates = count == 0 ? [] : list[..count];
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDivisible(ulong exponent, UInt128 divisor)
|
||||
{
|
||||
@@ -52,7 +52,7 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
return divisible;
|
||||
}
|
||||
|
||||
private static (ulong divisor, ulong cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, ulong cycle)>();
|
||||
private static (ulong divisor, uint cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, uint cycle)>();
|
||||
|
||||
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
|
||||
{
|
||||
@@ -68,17 +68,17 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_divisorCandidates is { Length: > 0 } candidates)
|
||||
{
|
||||
int len = candidates.Length;
|
||||
ulong cycle;
|
||||
for (int k = 0; k < len; k++)
|
||||
{
|
||||
(ulong dSmall, cycle) = candidates[k];
|
||||
if (p % cycle != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_divisorCandidates is { Length: > 0 } candidates)
|
||||
{
|
||||
int len = candidates.Length;
|
||||
uint cycle;
|
||||
for (int k = 0; k < len; k++)
|
||||
{
|
||||
(ulong dSmall, cycle) = candidates[k];
|
||||
if (p % cycle != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsDivisible(p, dSmall))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong montgomeryOne, ulong montgomeryTwo)
|
||||
{
|
||||
public ulong Modulus { get; } = modulus;
|
||||
|
||||
public ulong NPrime { get; } = nPrime;
|
||||
|
||||
public ulong MontgomeryOne { get; } = montgomeryOne;
|
||||
|
||||
public ulong MontgomeryTwo { get; } = montgomeryTwo;
|
||||
}
|
||||
|
||||
internal static class MontgomeryDivisorDataCache
|
||||
{
|
||||
private const ulong BaseBlockEnd = PerfectNumberConstants.MaxQForDivisorCycles;
|
||||
|
||||
private static readonly ConcurrentDictionary<ulong, CacheEntry> Cache = new();
|
||||
private static readonly ConcurrentDictionary<int, ConcurrentDictionary<ulong, byte>> BlockMembership = new();
|
||||
|
||||
public static MontgomeryDivisorData Get(ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL);
|
||||
}
|
||||
|
||||
int blockIndex = ComputeBlockIndex(modulus);
|
||||
return GetManaged(modulus, blockIndex);
|
||||
}
|
||||
|
||||
internal static void ReleaseBlock(DivisorCycleCache.CycleBlock block)
|
||||
{
|
||||
if (block.Index <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (BlockMembership.TryRemove(block.Index, out ConcurrentDictionary<ulong, byte>? members))
|
||||
{
|
||||
foreach (ulong modulus in members.Keys)
|
||||
{
|
||||
Cache.TryRemove(modulus, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MontgomeryDivisorData GetManaged(ulong modulus, int blockIndex)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (Cache.TryGetValue(modulus, out CacheEntry entry))
|
||||
{
|
||||
if (entry.BlockIndex != blockIndex)
|
||||
{
|
||||
if (blockIndex > 0 && entry.BlockIndex <= 0)
|
||||
{
|
||||
CacheEntry updated = new(entry.Data, blockIndex);
|
||||
if (Cache.TryUpdate(modulus, updated, entry))
|
||||
{
|
||||
RegisterBlockMembership(modulus, blockIndex);
|
||||
return entry.Data;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blockIndex > 0)
|
||||
{
|
||||
RegisterBlockMembership(modulus, blockIndex);
|
||||
}
|
||||
|
||||
return entry.Data;
|
||||
}
|
||||
return entry.Data;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData created = Create(modulus);
|
||||
if (Cache.TryAdd(modulus, new CacheEntry(created, blockIndex)))
|
||||
{
|
||||
RegisterBlockMembership(modulus, blockIndex);
|
||||
return created;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RegisterBlockMembership(ulong modulus, int blockIndex)
|
||||
{
|
||||
if (blockIndex <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ConcurrentDictionary<ulong, byte> set = BlockMembership.GetOrAdd(blockIndex, static _ => new ConcurrentDictionary<ulong, byte>());
|
||||
set.TryAdd(modulus, 0);
|
||||
}
|
||||
|
||||
private static int ComputeBlockIndex(ulong modulus)
|
||||
{
|
||||
if (modulus <= BaseBlockEnd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)((modulus - (BaseBlockEnd + 1UL)) / (ulong)PerfectNumberConstants.MaxQForDivisorCycles) + 1;
|
||||
}
|
||||
|
||||
private static MontgomeryDivisorData Create(ulong modulus)
|
||||
{
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
ComputeMontgomeryNPrime(modulus),
|
||||
ComputeMontgomeryResidue(1UL, modulus),
|
||||
ComputeMontgomeryResidue(2UL, modulus));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
||||
|
||||
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||||
{
|
||||
ulong inv = modulus;
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
|
||||
private readonly struct CacheEntry
|
||||
{
|
||||
internal CacheEntry(MontgomeryDivisorData data, int blockIndex)
|
||||
{
|
||||
Data = data;
|
||||
BlockIndex = blockIndex;
|
||||
}
|
||||
|
||||
internal MontgomeryDivisorData Data { get; }
|
||||
|
||||
internal int BlockIndex { get; }
|
||||
}
|
||||
}
|
||||
@@ -214,7 +214,7 @@ public static class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryMod(this ulong exponent, in MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData divisor)
|
||||
public static ulong Pow2MontgomeryMod(this ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
@@ -247,7 +247,7 @@ public static class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWithCycle(this ulong exponent, ulong cycleLength, in MersenneNumberDivisorByDivisorGpuTester.MontgomeryDivisorData divisor)
|
||||
public static ulong Pow2MontgomeryModWithCycle(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
if (cycleLength == 0UL)
|
||||
{
|
||||
@@ -255,12 +255,18 @@ public static class ULongExtensions
|
||||
}
|
||||
|
||||
ulong rotationCount = exponent % cycleLength;
|
||||
if (rotationCount == 0UL)
|
||||
return rotationCount.Pow2MontgomeryModFromCycleRemainder(divisor);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModFromCycleRemainder(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
if (reducedExponent == 0UL)
|
||||
{
|
||||
return 1UL;
|
||||
}
|
||||
|
||||
return rotationCount.Pow2MontgomeryMod(divisor);
|
||||
return reducedExponent.Pow2MontgomeryMod(divisor);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
Reference in new issue
Block a user