Implement multi-tier divisor cycle caching and per-thread reuse

This commit is contained in:
Marek Lemańczyk committed 2025-09-24 19:04:07 +02:00
1 parent e92bca197d
commit d4dcd0a534
8 files changed
+585 -145

No files matched your search

+80 -34
View File
@@ -447,16 +447,18 @@ internal static class Program
} }
Console.WriteLine($"Loading divisor cycles into memory..."); Console.WriteLine($"Loading divisor cycles into memory...");
if (string.Equals(Path.GetExtension(cyclesPath), ".csv", StringComparison.OrdinalIgnoreCase)) if (string.Equals(Path.GetExtension(cyclesPath), ".csv", StringComparison.OrdinalIgnoreCase))
{ {
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath); MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
} }
else else
{ {
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath); MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
} }
Console.WriteLine("Divisor cycles are ready"); DivisorCycleCache.Shared.ReloadFromCurrentSnapshot();
Console.WriteLine("Divisor cycles are ready");
if (useByDivisor) if (useByDivisor)
{ {
@@ -882,12 +884,18 @@ internal static class Program
bool exhausted = false; bool exhausted = false;
ulong divisor = 0UL; ulong divisor = 0UL;
int activeCount = 0; int activeCount = 0;
Span<byte> hitsSpan = default; Span<byte> hitsSpan = default;
int hitIndex = 0; int hitIndex = 0;
int index = 0; int index = 0;
bool useCycleFilter = _byDivisorTester!.UseDivisorCycles;
DivisorCycleCache.Lease cycleLease = default;
ulong cycleLeaseStart = 0UL;
ulong cycleLeaseEnd = 0UL;
ulong[]? cycleValues = null;
ulong divisorCycle = 0UL;
try try
{ {
while (true) while (true)
{ {
if (Volatile.Read(ref remainingStates) == 0) if (Volatile.Read(ref remainingStates) == 0)
@@ -900,11 +908,11 @@ internal static class Program
divisor = AcquireNextDivisor(ref nextDivisor, divisorLimit, ref divisorsExhaustedFlag, ref finalDivisorBits, out exhausted, ref localDivisorCursor, ref localDivisorsRemaining); divisor = AcquireNextDivisor(ref nextDivisor, divisorLimit, ref divisorsExhaustedFlag, ref finalDivisorBits, out exhausted, ref localDivisorCursor, ref localDivisorsRemaining);
if (divisor == 0UL) if (divisor == 0UL)
{ {
if (!exhausted) if (!exhausted)
{ {
if (Volatile.Read(ref remainingStates) == 0) if (Volatile.Read(ref remainingStates) == 0)
{ {
break; break;
} }
@@ -937,11 +945,48 @@ internal static class Program
break; break;
} }
continue; continue;
} }
// Can we prepare as many buffers as many threads were requested upfront and have them prepared in separate task, so that they're always ready when needed? if (useCycleFilter)
activeCount = BuildPrimeBuffer(divisor, primeValues, allowedMaxValues, stateFlags, primeBuffer, indexBuffer, completionsBuffer, ref completionsCount, ref remainingStates, activeStateMask, ref activeStartIndex); {
if (!cycleLease.IsValid || divisor < cycleLeaseStart || divisor > cycleLeaseEnd)
{
cycleLease.Dispose();
cycleLease = DivisorCycleCache.Shared.Acquire(divisor);
cycleLeaseStart = cycleLease.Start;
cycleLeaseEnd = cycleLease.End;
cycleValues = cycleLease.Values;
}
else if (cycleValues is null)
{
cycleValues = cycleLease.Values;
}
if (cycleValues is not null && divisor >= cycleLeaseStart)
{
ulong offset = divisor - cycleLeaseStart;
if (offset < (ulong)cycleValues.Length)
{
divisorCycle = cycleValues[(int)offset];
}
else
{
divisorCycle = cycleLease.GetCycle(divisor);
}
}
else
{
divisorCycle = cycleLease.GetCycle(divisor);
}
}
else
{
divisorCycle = 0UL;
}
// Can we prepare as many buffers as many threads were requested upfront and have them prepared in separate task, so that they're always ready when needed?
activeCount = BuildPrimeBuffer(divisor, primeValues, allowedMaxValues, stateFlags, primeBuffer, indexBuffer, completionsBuffer, ref completionsCount, ref remainingStates, activeStateMask, ref activeStartIndex);
if (completionsCount > 0) if (completionsCount > 0)
{ {
@@ -953,9 +998,9 @@ internal static class Program
continue; continue;
} }
hitsSpan = hitsBuffer.AsSpan(0, activeCount); hitsSpan = hitsBuffer.AsSpan(0, activeCount);
hitsSpan.Clear(); hitsSpan.Clear();
session.CheckDivisor(divisor, primeBuffer.AsSpan(0, activeCount), hitsSpan); session.CheckDivisor(divisor, divisorCycle, primeBuffer.AsSpan(0, activeCount), hitsSpan);
for (hitIndex = 0; hitIndex < activeCount; hitIndex++) for (hitIndex = 0; hitIndex < activeCount; hitIndex++)
{ {
@@ -993,14 +1038,15 @@ internal static class Program
{ {
FlushPendingResults(compositesBuffer, ref compositesCount); FlushPendingResults(compositesBuffer, ref compositesCount);
} }
} }
finally finally
{ {
ArrayPool<PendingResult>.Shared.Return(compositesBuffer, clearArray: true); cycleLease.Dispose();
ArrayPool<PendingResult>.Shared.Return(completionsBuffer, clearArray: true); ArrayPool<PendingResult>.Shared.Return(compositesBuffer, clearArray: true);
ArrayPool<int>.Shared.Return(indexBuffer, clearArray: true); ArrayPool<PendingResult>.Shared.Return(completionsBuffer, clearArray: true);
ArrayPool<ulong>.Shared.Return(primeBuffer, clearArray: true); ArrayPool<int>.Shared.Return(indexBuffer, clearArray: true);
ArrayPool<byte>.Shared.Return(hitsBuffer, clearArray: true); ArrayPool<ulong>.Shared.Return(primeBuffer, clearArray: true);
ArrayPool<byte>.Shared.Return(hitsBuffer, clearArray: true);
} }
}); });
} }
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using System.Reflection; using System.Reflection;
using Xunit; using Xunit;
@@ -36,7 +37,7 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorGpuTester(); var tester = new MersenneNumberDivisorGpuTester();
typeof(MersenneNumberDivisorGpuTester) typeof(MersenneNumberDivisorGpuTester)
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)! .GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
.SetValue(null, Array.Empty<(ulong, uint)>()); .SetValue(null, Array.Empty<(ulong, ulong)>());
tester.IsPrime(11UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue(); tester.IsPrime(11UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeFalse(); divisorsExhausted.Should().BeFalse();
@@ -112,11 +113,13 @@ public class MersenneNumberDivisorGpuTesterTests
ulong[] primes = { 5UL, 7UL, 11UL, 13UL }; ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
byte[] hits = new byte[primes.Length]; byte[] hits = new byte[primes.Length];
session.CheckDivisor(23UL, primes, hits); ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
session.CheckDivisor(23UL, cycle23, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
Array.Fill(hits, (byte)0); Array.Fill(hits, (byte)0);
session.CheckDivisor(31UL, primes, hits); ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL);
session.CheckDivisor(31UL, cycle31, primes, hits);
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 }); hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
} }
} }
@@ -50,8 +50,8 @@ public class MersenneNumberOrderGpuTesterTests
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!; var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
var originalTable = ((List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!).Select(x => x).ToList(); var originalTable = ((List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!).Select(x => x).ToList();
var originalSmall = (uint[]?)smallCyclesField.GetValue(cycles); var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
uint[]? backupSmall = originalSmall is null ? null : (uint[])originalSmall.Clone(); ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
cycles.LoadFrom(tempPath); cycles.LoadFrom(tempPath);
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
+414
View File
@@ -0,0 +1,414 @@
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace PerfectNumbers.Core;
public sealed class DivisorCycleCache
{
internal sealed class CycleBlock
{
private int _referenceCount;
internal CycleBlock(int index, ulong start, ulong[] cycles)
{
Index = index;
Start = start;
Cycles = cycles;
End = start + (ulong)cycles.Length - 1UL;
}
internal int Index { get; }
internal ulong Start { get; }
internal ulong End { get; }
internal ulong[] Cycles { get; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void AddRef()
{
Interlocked.Increment(ref _referenceCount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Release()
{
Interlocked.Decrement(ref _referenceCount);
}
internal bool IsInUse => Volatile.Read(ref _referenceCount) > 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ulong GetCycle(ulong divisor)
{
if (divisor < Start || divisor > End)
{
return 0UL;
}
return Cycles[(int)(divisor - Start)];
}
}
public struct Lease : IDisposable
{
private readonly DivisorCycleCache? _owner;
private CycleBlock? _block;
internal Lease(DivisorCycleCache owner, CycleBlock block)
{
_owner = owner;
_block = block;
_block.AddRef();
}
public bool IsValid => _block is not null;
public ulong Start => _block?.Start ?? 0UL;
public ulong End => _block?.End ?? 0UL;
public ulong[]? Values => _block?.Cycles;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetCycle(ulong divisor)
{
return _block?.GetCycle(divisor) ?? 0UL;
}
public void Dispose()
{
CycleBlock? block = _block;
if (block is null)
{
return;
}
block.Release();
_owner?.Release(block);
_block = null;
}
}
private readonly object _sync = new();
private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new();
private CycleBlock _baseBlock = null!;
private CycleBlock? _previous;
private CycleBlock? _current;
private CycleBlock? _next;
private bool _initialized;
private static readonly Lazy<DivisorCycleCache> _lazy = new(() => new DivisorCycleCache());
public static DivisorCycleCache Shared => _lazy.Value;
private DivisorCycleCache()
{
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
Initialize(snapshot);
}
public void ReloadFromCurrentSnapshot()
{
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
Initialize(snapshot);
}
private void Initialize(ulong[] snapshot)
{
lock (_sync)
{
_pending.Clear();
_baseBlock = new CycleBlock(index: 0, start: 0UL, snapshot);
_previous = _baseBlock;
_current = null;
_next = null;
_initialized = true;
StartPrefetchLocked(1);
}
}
public Lease Acquire(ulong divisor)
{
if (!_initialized)
{
throw new InvalidOperationException("DivisorCycleCache must be initialized before use.");
}
if (divisor <= _baseBlock.End)
{
return new Lease(this, _baseBlock);
}
CycleBlock block = AcquireDynamicBlock(divisor);
return new Lease(this, block);
}
private CycleBlock AcquireDynamicBlock(ulong divisor)
{
int blockIndex = GetBlockIndex(divisor);
while (true)
{
Task<CycleBlock>? taskToAwait = null;
lock (_sync)
{
if (TryGetCachedBlockLocked(blockIndex, out CycleBlock? cached))
{
return cached!;
}
if (!_pending.TryGetValue(blockIndex, out taskToAwait))
{
taskToAwait = Task.Run(() => GenerateBlock(blockIndex));
_pending[blockIndex] = taskToAwait;
}
}
CycleBlock block = taskToAwait.GetAwaiter().GetResult();
lock (_sync)
{
_pending.TryRemove(blockIndex, out _);
if (TryGetCachedBlockLocked(blockIndex, out CycleBlock? cachedAfterAwait))
{
return cachedAfterAwait!;
}
PromoteBlockLocked(block);
return block;
}
}
}
private bool TryGetCachedBlockLocked(int blockIndex, out CycleBlock? block)
{
if (_current is { Index: var currentIndex } current && currentIndex == blockIndex)
{
block = current;
return true;
}
if (_previous is { Index: var previousIndex } previous && previousIndex == blockIndex)
{
block = previous;
return true;
}
if (_next is { Index: var nextIndex } next && nextIndex == blockIndex)
{
PromoteNextLocked();
block = _current!;
return true;
}
block = null;
return false;
}
private void PromoteNextLocked()
{
if (_next is null)
{
return;
}
CycleBlock? oldPrevious = _previous;
_previous = _current ?? _baseBlock;
_current = _next;
_next = null;
StartPrefetchLocked(_current!.Index + 1);
TryReleaseBlockLocked(oldPrevious);
}
private void PromoteBlockLocked(CycleBlock block)
{
if (_current is null)
{
_previous = _baseBlock;
_current = block;
StartPrefetchLocked(block.Index + 1);
return;
}
if (block.Index > _current.Index)
{
CycleBlock? oldPrevious = _previous;
_previous = _current;
_current = block;
StartPrefetchLocked(block.Index + 1);
TryReleaseBlockLocked(oldPrevious);
return;
}
if (_previous is null || block.Index >= _previous.Index)
{
CycleBlock? oldPrevious = _previous;
_previous = block;
TryReleaseBlockLocked(oldPrevious);
}
}
private void StartPrefetchLocked(int blockIndex)
{
if (blockIndex <= 0)
{
return;
}
if (_next is { Index: var nextIndex } next && nextIndex == blockIndex)
{
return;
}
if (_pending.TryGetValue(blockIndex, out Task<CycleBlock>? existing))
{
if (existing.IsCompletedSuccessfully)
{
if (_next is null || _next.Index < blockIndex)
{
_next = existing.Result;
}
_pending.TryRemove(blockIndex, out _);
}
return;
}
Task<CycleBlock> task = Task.Run(() => GenerateBlock(blockIndex));
_pending[blockIndex] = task;
task.ContinueWith(t => OnPrefetchCompleted(blockIndex, t), TaskScheduler.Default);
}
private void OnPrefetchCompleted(int blockIndex, Task<CycleBlock> task)
{
if (!task.IsCompletedSuccessfully)
{
_pending.TryRemove(blockIndex, out _);
return;
}
lock (_sync)
{
if (_next is null || _next.Index < blockIndex)
{
_next = task.Result;
}
_pending.TryRemove(blockIndex, out _);
TrimCacheLocked();
}
}
private void TrimCacheLocked()
{
if (_previous is null)
{
return;
}
if (ReferenceEquals(_previous, _baseBlock))
{
return;
}
if (_previous.IsInUse)
{
return;
}
if (_next is not null && _current is not null)
{
_previous = null;
}
}
private void TryReleaseBlockLocked(CycleBlock? block)
{
if (block is null || ReferenceEquals(block, _baseBlock))
{
return;
}
if (ReferenceEquals(block, _current) || ReferenceEquals(block, _next))
{
return;
}
if (block.IsInUse)
{
return;
}
if (ReferenceEquals(block, _previous))
{
_previous = null;
}
}
private void Release(CycleBlock block)
{
if (!block.IsInUse)
{
lock (_sync)
{
TryReleaseBlockLocked(block);
}
}
}
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)
{
if (blockIndex == 0)
{
return _baseBlock.Cycles.Length;
}
return PerfectNumberConstants.MaxQForDivisorCycles;
}
private ulong GetBlockStart(int blockIndex)
{
if (blockIndex == 0)
{
return 0UL;
}
ulong baseEnd = _baseBlock.End;
return baseEnd + 1UL + (ulong)(blockIndex - 1) * (ulong)PerfectNumberConstants.MaxQForDivisorCycles;
}
private int GetBlockIndex(ulong divisor)
{
if (divisor <= _baseBlock.End)
{
return 0;
}
ulong offset = divisor - (_baseBlock.End + 1UL);
return (int)(offset / (ulong)PerfectNumberConstants.MaxQForDivisorCycles) + 1;
}
}
+38 -54
View File
@@ -27,19 +27,19 @@ public sealed class KernelContainer
{ {
// Serializes first-time initialization of kernels/buffers per accelerator. // Serializes first-time initialization of kernels/buffers per accelerator.
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order; public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>? Incremental; ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>,
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod; ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>? IncrementalOrder; ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>? Pow2ModOrder; ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length. // Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallCycles; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne; public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven; public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
@@ -123,7 +123,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
} }
} }
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel
{ {
get get
{ {
@@ -132,15 +132,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
{ {
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () => return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>(); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
}); });
} }
} }
} }
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>> IncrementalKernel public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel
{ {
get get
{ {
@@ -149,15 +149,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
{ {
return KernelContainer.InitOnce(ref _kernels.Incremental, () => return KernelContainer.InitOnce(ref _kernels.Incremental, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>(IncrementalKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>>(); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
}); });
} }
} }
} }
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>> IncrementalOrderKernel public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel
{ {
get get
{ {
@@ -166,15 +166,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
{ {
return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () => return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>(IncrementalOrderKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>>(); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
}); });
} }
} }
} }
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>> Pow2ModOrderKernel public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel
{ {
get get
{ {
@@ -183,9 +183,9 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
{ {
return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () => return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>(Pow2ModOrderKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<uint, Stride1D.Dense>>>(); return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
}); });
} }
} }
@@ -196,7 +196,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
private static void IncrementalKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong divMul, private static void IncrementalKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong divMul,
ulong q0m10, ulong q0m8, ulong q0m3, ulong q0m5, ArrayView<ulong> orders, ulong q0m10, ulong q0m8, ulong q0m3, ulong q0m5, ArrayView<ulong> orders,
ArrayView1D<uint, Stride1D.Dense> smallCycles) ArrayView1D<ulong, Stride1D.Dense> smallCycles)
{ {
ulong idx = (ulong)index.X; ulong idx = (ulong)index.X;
ulong idxMod3 = idx % 3UL; ulong idxMod3 = idx % 3UL;
@@ -246,15 +246,11 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
// Small-cycles in-kernel early rejection from device table // Small-cycles in-kernel early rejection from device table
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length) if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{ {
uint cyc = smallCycles[(int)q.Low]; ulong cycle = smallCycles[(int)q.Low];
if (cyc != 0U) if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{ {
ulong cycle = cyc; orders[index] = 0UL;
if (cycle <= exponent && (exponent % cycle) != 0UL) return;
{
orders[index] = 0UL;
return;
}
} }
} }
@@ -352,7 +348,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
private static void Pow2ModKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _, private static void Pow2ModKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _,
ResidueAutomatonArgs ra, ArrayView<ulong> orders, ResidueAutomatonArgs ra, ArrayView<ulong> orders,
ArrayView1D<uint, Stride1D.Dense> smallCycles, ArrayView1D<ulong, Stride1D.Dense> smallCycles,
ArrayView1D<uint, Stride1D.Dense> smallPrimesLastOne, ArrayView1D<uint, Stride1D.Dense> smallPrimesLastOne,
ArrayView1D<uint, Stride1D.Dense> smallPrimesLastSeven, ArrayView1D<uint, Stride1D.Dense> smallPrimesLastSeven,
ArrayView1D<ulong, Stride1D.Dense> smallPrimesPow2LastOne, ArrayView1D<ulong, Stride1D.Dense> smallPrimesPow2LastOne,
@@ -402,15 +398,11 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One; GpuUInt128 q = twoP.Mul64(k) + GpuUInt128.One;
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length) if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{ {
uint cyc = smallCycles[(int)q.Low]; ulong cycle = smallCycles[(int)q.Low];
if (cyc != 0U) if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{ {
ulong cycle = cyc; orders[index] = 0UL;
if (cycle <= exponent && (exponent % cycle) != 0UL) return;
{
orders[index] = 0UL;
return;
}
} }
} }
if (GpuUInt128.Pow2Minus1Mod(exponent, q) != GpuUInt128.Zero) if (GpuUInt128.Pow2Minus1Mod(exponent, q) != GpuUInt128.Zero)
@@ -489,7 +481,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
} }
private static void IncrementalOrderKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong divMul, private static void IncrementalOrderKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong divMul,
ResidueAutomatonArgs ra, ArrayView<int> found, ArrayView1D<uint, Stride1D.Dense> smallCycles) ResidueAutomatonArgs ra, ArrayView<int> found, ArrayView1D<ulong, Stride1D.Dense> smallCycles)
{ {
ulong idx = (ulong)index.X; ulong idx = (ulong)index.X;
ulong idxMod3 = idx % 3UL; ulong idxMod3 = idx % 3UL;
@@ -536,14 +528,10 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
// Small-cycles in-kernel early rejection from device table // Small-cycles in-kernel early rejection from device table
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length) if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{ {
uint cyc = smallCycles[(int)q.Low]; ulong cycle = smallCycles[(int)q.Low];
if (cyc != 0U) if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{ {
ulong cycle = cyc; return;
if (cycle <= exponent && (exponent % cycle) != 0UL)
{
return;
}
} }
} }
GpuUInt128 phi = q - GpuUInt128.One; GpuUInt128 phi = q - GpuUInt128.One;
@@ -575,7 +563,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
} }
private static void Pow2ModOrderKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _, private static void Pow2ModOrderKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _,
ResidueAutomatonArgs ra, ArrayView<int> found, ArrayView1D<uint, Stride1D.Dense> smallCycles) ResidueAutomatonArgs ra, ArrayView<int> found, ArrayView1D<ulong, Stride1D.Dense> smallCycles)
{ {
ulong idx = (ulong)index.X; ulong idx = (ulong)index.X;
ulong idxMod3 = idx % 3UL; ulong idxMod3 = idx % 3UL;
@@ -621,14 +609,10 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
// Small-cycles in-kernel early rejection from device table // Small-cycles in-kernel early rejection from device table
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length) if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{ {
uint cyc = smallCycles[(int)q.Low]; ulong cycle = smallCycles[(int)q.Low];
if (cyc != 0U) if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{ {
ulong cycle = cyc; return;
if (cycle <= exponent && (exponent % cycle) != 0UL)
{
return;
}
} }
} }
if (GpuUInt128.Pow2Mod(exponent, q) != GpuUInt128.One) if (GpuUInt128.Pow2Mod(exponent, q) != GpuUInt128.One)
@@ -681,7 +665,7 @@ public class GpuKernelPool
// Ensures the small cycles table is uploaded to the device for the given accelerator. // Ensures the small cycles table is uploaded to the device for the given accelerator.
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it). // Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
public static ArrayView1D<uint, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator) public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator)
{ {
var kernels = GetKernels(accelerator); var kernels = GetKernels(accelerator);
if (kernels.SmallCycles is { } buffer) if (kernels.SmallCycles is { } buffer)
@@ -698,7 +682,7 @@ public class GpuKernelPool
} }
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
var device = accelerator.Allocate1D<uint>(host.Length); var device = accelerator.Allocate1D<ulong>(host.Length);
device.View.CopyFromCPU(host); device.View.CopyFromCPU(host);
kernels.SmallCycles = device; kernels.SmallCycles = device;
return device.View; return device.View;
@@ -389,12 +389,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
_positionBuffer = ArrayPool<int>.Shared.Rent(_capacity); _positionBuffer = ArrayPool<int>.Shared.Rent(_capacity);
} }
public void CheckDivisor(ulong divisor, ReadOnlySpan<ulong> primes, Span<byte> hits) public void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
{ {
if (_disposed) if (_disposed)
{ {
throw new ObjectDisposedException(nameof(DivisorScanSession)); throw new ObjectDisposedException(nameof(DivisorScanSession));
} }
int primesLength = primes.Length; int primesLength = primes.Length;
if (primesLength == 0) if (primesLength == 0)
@@ -413,14 +413,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
return; return;
} }
bool useCycles = owner._useDivisorCycles; bool cycleEnabled = owner._useDivisorCycles && divisorCycle != 0UL;
ulong cycle = 0UL;
bool cycleEnabled = false;
if (useCycles)
{
cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(modulus);
cycleEnabled = cycle != 0UL;
}
Accelerator accelerator = _accelerator; Accelerator accelerator = _accelerator;
Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> kernel = _kernel; Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> kernel = _kernel;
@@ -448,11 +441,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
for (int i = 0; i < batchSize; i++) for (int i = 0; i < batchSize; i++)
{ {
ulong prime = primesSlice[i]; ulong prime = primesSlice[i];
if (cycleEnabled && prime % cycle != 0UL) if (cycleEnabled && prime % divisorCycle != 0UL)
{ {
hitsSlice[i] = 0; hitsSlice[i] = 0;
continue; continue;
} }
primeSpan[computeCount] = prime; primeSpan[computeCount] = prime;
positionSpan[computeCount] = i; positionSpan[computeCount] = i;
@@ -15,9 +15,9 @@ public sealed class MersenneNumberDivisorGpuTester
public static void BuildDivisorCandidates() public static void BuildDivisorCandidates()
{ {
uint[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2]; (ulong divisor, ulong cycle)[] list = new (ulong divisor, ulong cycle)[snapshot.Length / 2];
uint cycle; ulong cycle;
int count = 0, i, snapshotLength = snapshot.Length; int count = 0, i, snapshotLength = snapshot.Length;
for (i = 3; i < snapshotLength; i += 2) for (i = 3; i < snapshotLength; i += 2)
{ {
@@ -30,7 +30,7 @@ public sealed class MersenneNumberDivisorGpuTester
list[count++] = ((ulong)i, cycle); list[count++] = ((ulong)i, cycle);
} }
_divisorCandidates = count == 0 ? [] : list[..count]; _divisorCandidates = count == 0 ? [] : list[..count];
} }
public bool IsDivisible(ulong exponent, UInt128 divisor) public bool IsDivisible(ulong exponent, UInt128 divisor)
@@ -52,7 +52,7 @@ public sealed class MersenneNumberDivisorGpuTester
return divisible; return divisible;
} }
private static (ulong divisor, uint cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, uint cycle)>(); private static (ulong divisor, ulong cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, ulong cycle)>();
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted) public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
{ {
@@ -71,7 +71,7 @@ public sealed class MersenneNumberDivisorGpuTester
if (_divisorCandidates is { Length: > 0 } candidates) if (_divisorCandidates is { Length: > 0 } candidates)
{ {
int len = candidates.Length; int len = candidates.Length;
uint cycle; ulong cycle;
for (int k = 0; k < len; k++) for (int k = 0; k < len; k++)
{ {
(ulong dSmall, cycle) = candidates[k]; (ulong dSmall, cycle) = candidates[k];
+27 -27
View File
@@ -9,9 +9,9 @@ namespace PerfectNumbers.Core;
public class MersenneDivisorCycles public class MersenneDivisorCycles
{ {
private List<(ulong divisor, ulong cycleLength)> _table = []; private List<(ulong divisor, ulong cycleLength)> _table = [];
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown // Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
private uint[]? _smallCycles; private ulong[]? _smallCycles;
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles(); public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
@@ -22,15 +22,15 @@ public class MersenneDivisorCycles
public void LoadFrom(string path) public void LoadFrom(string path)
{ {
EnsureSmallBuffer(); EnsureSmallBuffer();
List<(ulong divisor, ulong cycle)> cycles = []; List<(ulong divisor, ulong cycle)> cycles = [];
using Stream outputStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize10M, useAsync: false); using Stream outputStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize10M, useAsync: false);
foreach (var (d, c) in EnumerateStream(outputStream)) foreach (var (d, c) in EnumerateStream(outputStream))
{ {
cycles.Add((d, c)); cycles.Add((d, c));
if (d <= PerfectNumberConstants.MaxQForDivisorCycles) if (d <= PerfectNumberConstants.MaxQForDivisorCycles)
{ {
_smallCycles![(int)d] = (uint)Math.Min(uint.MaxValue, c == 0UL ? 1UL : c); _smallCycles![(int)d] = c == 0UL ? 1UL : c;
} }
} }
cycles.Sort((a, b) => a.divisor < b.divisor ? -1 : (a.divisor > b.divisor ? 1 : 0)); cycles.Sort((a, b) => a.divisor < b.divisor ? -1 : (a.divisor > b.divisor ? 1 : 0));
@@ -41,13 +41,13 @@ public class MersenneDivisorCycles
// Provides a snapshot array of small cycles [0..PerfectNumberConstants.MaxQForDivisorCycles]. Index is the divisor. // Provides a snapshot array of small cycles [0..PerfectNumberConstants.MaxQForDivisorCycles]. Index is the divisor.
// The returned array is a copy safe for use across threads and device uploads. // The returned array is a copy safe for use across threads and device uploads.
public uint[] ExportSmallCyclesSnapshot() public ulong[] ExportSmallCyclesSnapshot()
{ {
EnsureSmallBuffer(); EnsureSmallBuffer();
uint[] snapshot = new uint[PerfectNumberConstants.MaxQForDivisorCycles + 1]; ulong[] snapshot = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
Array.Copy(_smallCycles!, snapshot, snapshot.Length); Array.Copy(_smallCycles!, snapshot, snapshot.Length);
return snapshot; return snapshot;
} }
public static IEnumerable<(ulong divisor, ulong cycleLength)> EnumerateStream(Stream compressor) public static IEnumerable<(ulong divisor, ulong cycleLength)> EnumerateStream(Stream compressor)
{ {
@@ -75,14 +75,14 @@ public class MersenneDivisorCycles
// Fast-path: in-memory array for small divisors // Fast-path: in-memory array for small divisors
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles) if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
{ {
var arr = _smallCycles; var arr = _smallCycles;
if (arr is not null) if (arr is not null)
{ {
uint cached = arr[(int)divisor]; ulong cached = arr[(int)divisor];
if (cached != 0U) if (cached != 0UL)
{ {
return cached; return cached;
} }
} }
} }
@@ -112,7 +112,7 @@ public class MersenneDivisorCycles
} }
} }
return CalculateCycleLength(divisor); return CalculateCycleLength(divisor);
} }
public static UInt128 GetCycle(UInt128 divisor) public static UInt128 GetCycle(UInt128 divisor)
@@ -396,9 +396,9 @@ public class MersenneDivisorCycles
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureSmallBuffer() private void EnsureSmallBuffer()
{ {
if (_smallCycles is null) if (_smallCycles is null)
{ {
_smallCycles = new uint[PerfectNumberConstants.MaxQForDivisorCycles + 1]; _smallCycles = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
} }
} }
} }