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...");
if (string.Equals(Path.GetExtension(cyclesPath), ".csv", StringComparison.OrdinalIgnoreCase))
{
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
}
else
{
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
}
if (string.Equals(Path.GetExtension(cyclesPath), ".csv", StringComparison.OrdinalIgnoreCase))
{
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
}
else
{
MersenneDivisorCycles.Shared.LoadFrom(cyclesPath);
}
Console.WriteLine("Divisor cycles are ready");
DivisorCycleCache.Shared.ReloadFromCurrentSnapshot();
Console.WriteLine("Divisor cycles are ready");
if (useByDivisor)
{
@@ -882,12 +884,18 @@ internal static class Program
bool exhausted = false;
ulong divisor = 0UL;
int activeCount = 0;
Span<byte> hitsSpan = default;
int hitIndex = 0;
int index = 0;
Span<byte> hitsSpan = default;
int hitIndex = 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)
{
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);
if (divisor == 0UL)
{
if (!exhausted)
{
if (Volatile.Read(ref remainingStates) == 0)
if (divisor == 0UL)
{
if (!exhausted)
{
if (Volatile.Read(ref remainingStates) == 0)
{
break;
}
@@ -937,11 +945,48 @@ internal static class Program
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?
activeCount = BuildPrimeBuffer(divisor, primeValues, allowedMaxValues, stateFlags, primeBuffer, indexBuffer, completionsBuffer, ref completionsCount, ref remainingStates, activeStateMask, ref activeStartIndex);
if (useCycleFilter)
{
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)
{
@@ -953,9 +998,9 @@ internal static class Program
continue;
}
hitsSpan = hitsBuffer.AsSpan(0, activeCount);
hitsSpan.Clear();
session.CheckDivisor(divisor, primeBuffer.AsSpan(0, activeCount), hitsSpan);
hitsSpan = hitsBuffer.AsSpan(0, activeCount);
hitsSpan.Clear();
session.CheckDivisor(divisor, divisorCycle, primeBuffer.AsSpan(0, activeCount), hitsSpan);
for (hitIndex = 0; hitIndex < activeCount; hitIndex++)
{
@@ -993,14 +1038,15 @@ internal static class Program
{
FlushPendingResults(compositesBuffer, ref compositesCount);
}
}
finally
{
ArrayPool<PendingResult>.Shared.Return(compositesBuffer, clearArray: true);
ArrayPool<PendingResult>.Shared.Return(completionsBuffer, clearArray: true);
ArrayPool<int>.Shared.Return(indexBuffer, clearArray: true);
ArrayPool<ulong>.Shared.Return(primeBuffer, clearArray: true);
ArrayPool<byte>.Shared.Return(hitsBuffer, clearArray: true);
}
finally
{
cycleLease.Dispose();
ArrayPool<PendingResult>.Shared.Return(compositesBuffer, clearArray: true);
ArrayPool<PendingResult>.Shared.Return(completionsBuffer, clearArray: true);
ArrayPool<int>.Shared.Return(indexBuffer, clearArray: true);
ArrayPool<ulong>.Shared.Return(primeBuffer, clearArray: true);
ArrayPool<byte>.Shared.Return(hitsBuffer, clearArray: true);
}
});
}
@@ -1,4 +1,5 @@
using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
using System.Reflection;
using Xunit;
@@ -36,7 +37,7 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorGpuTester();
typeof(MersenneNumberDivisorGpuTester)
.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();
divisorsExhausted.Should().BeFalse();
@@ -112,11 +113,13 @@ public class MersenneNumberDivisorGpuTesterTests
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
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 });
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 });
}
}
@@ -50,8 +50,8 @@ public class MersenneNumberOrderGpuTesterTests
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 originalSmall = (uint[]?)smallCyclesField.GetValue(cycles);
uint[]? backupSmall = originalSmall is null ? null : (uint[])originalSmall.Clone();
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
cycles.LoadFrom(tempPath);
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.
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,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>>? Incremental;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<uint, Stride1D.Dense>,
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
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,
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.
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
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
{
@@ -132,15 +132,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
{
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);
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
{
@@ -149,15 +149,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
{
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);
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
{
@@ -166,15 +166,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
{
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);
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
{
@@ -183,9 +183,9 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
{
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);
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,
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 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
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{
uint cyc = smallCycles[(int)q.Low];
if (cyc != 0U)
ulong cycle = smallCycles[(int)q.Low];
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{
ulong cycle = cyc;
if (cycle <= exponent && (exponent % cycle) != 0UL)
{
orders[index] = 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 _,
ResidueAutomatonArgs ra, ArrayView<ulong> orders,
ArrayView1D<uint, Stride1D.Dense> smallCycles,
ArrayView1D<ulong, Stride1D.Dense> smallCycles,
ArrayView1D<uint, Stride1D.Dense> smallPrimesLastOne,
ArrayView1D<uint, Stride1D.Dense> smallPrimesLastSeven,
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;
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{
uint cyc = smallCycles[(int)q.Low];
if (cyc != 0U)
ulong cycle = smallCycles[(int)q.Low];
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{
ulong cycle = cyc;
if (cycle <= exponent && (exponent % cycle) != 0UL)
{
orders[index] = 0UL;
return;
}
orders[index] = 0UL;
return;
}
}
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,
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 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
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{
uint cyc = smallCycles[(int)q.Low];
if (cyc != 0U)
ulong cycle = smallCycles[(int)q.Low];
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{
ulong cycle = cyc;
if (cycle <= exponent && (exponent % cycle) != 0UL)
{
return;
}
return;
}
}
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 _,
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 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
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{
uint cyc = smallCycles[(int)q.Low];
if (cyc != 0U)
ulong cycle = smallCycles[(int)q.Low];
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{
ulong cycle = cyc;
if (cycle <= exponent && (exponent % cycle) != 0UL)
{
return;
}
return;
}
}
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.
// 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);
if (kernels.SmallCycles is { } buffer)
@@ -698,7 +682,7 @@ public class GpuKernelPool
}
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
var device = accelerator.Allocate1D<uint>(host.Length);
var device = accelerator.Allocate1D<ulong>(host.Length);
device.View.CopyFromCPU(host);
kernels.SmallCycles = device;
return device.View;
@@ -389,12 +389,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
_positionBuffer = ArrayPool<int>.Shared.Rent(_capacity);
}
public void CheckDivisor(ulong divisor, ReadOnlySpan<ulong> primes, Span<byte> hits)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DivisorScanSession));
}
public void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DivisorScanSession));
}
int primesLength = primes.Length;
if (primesLength == 0)
@@ -413,14 +413,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
return;
}
bool useCycles = owner._useDivisorCycles;
ulong cycle = 0UL;
bool cycleEnabled = false;
if (useCycles)
{
cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(modulus);
cycleEnabled = cycle != 0UL;
}
bool cycleEnabled = owner._useDivisorCycles && divisorCycle != 0UL;
Accelerator accelerator = _accelerator;
Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> kernel = _kernel;
@@ -448,11 +441,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
for (int i = 0; i < batchSize; i++)
{
ulong prime = primesSlice[i];
if (cycleEnabled && prime % cycle != 0UL)
{
hitsSlice[i] = 0;
continue;
}
if (cycleEnabled && prime % divisorCycle != 0UL)
{
hitsSlice[i] = 0;
continue;
}
primeSpan[computeCount] = prime;
positionSpan[computeCount] = i;
@@ -15,9 +15,9 @@ public sealed class MersenneNumberDivisorGpuTester
public static void BuildDivisorCandidates()
{
uint[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2];
uint cycle;
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
(ulong divisor, ulong cycle)[] list = new (ulong divisor, ulong cycle)[snapshot.Length / 2];
ulong cycle;
int count = 0, i, snapshotLength = snapshot.Length;
for (i = 3; i < snapshotLength; i += 2)
{
@@ -30,7 +30,7 @@ public sealed class MersenneNumberDivisorGpuTester
list[count++] = ((ulong)i, cycle);
}
_divisorCandidates = count == 0 ? [] : list[..count];
_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, 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)
{
@@ -71,7 +71,7 @@ public sealed class MersenneNumberDivisorGpuTester
if (_divisorCandidates is { Length: > 0 } candidates)
{
int len = candidates.Length;
uint cycle;
ulong cycle;
for (int k = 0; k < len; k++)
{
(ulong dSmall, cycle) = candidates[k];
+27 -27
View File
@@ -9,9 +9,9 @@ namespace PerfectNumbers.Core;
public class MersenneDivisorCycles
{
private List<(ulong divisor, ulong cycleLength)> _table = [];
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
private uint[]? _smallCycles;
private List<(ulong divisor, ulong cycleLength)> _table = [];
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
private ulong[]? _smallCycles;
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
@@ -22,15 +22,15 @@ public class MersenneDivisorCycles
public void LoadFrom(string path)
{
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);
foreach (var (d, c) in EnumerateStream(outputStream))
{
cycles.Add((d, c));
if (d <= PerfectNumberConstants.MaxQForDivisorCycles)
{
_smallCycles![(int)d] = (uint)Math.Min(uint.MaxValue, c == 0UL ? 1UL : c);
}
if (d <= PerfectNumberConstants.MaxQForDivisorCycles)
{
_smallCycles![(int)d] = c == 0UL ? 1UL : c;
}
}
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.
// The returned array is a copy safe for use across threads and device uploads.
public uint[] ExportSmallCyclesSnapshot()
{
EnsureSmallBuffer();
uint[] snapshot = new uint[PerfectNumberConstants.MaxQForDivisorCycles + 1];
Array.Copy(_smallCycles!, snapshot, snapshot.Length);
return snapshot;
}
public ulong[] ExportSmallCyclesSnapshot()
{
EnsureSmallBuffer();
ulong[] snapshot = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
Array.Copy(_smallCycles!, snapshot, snapshot.Length);
return snapshot;
}
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
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
{
var arr = _smallCycles;
var arr = _smallCycles;
if (arr is not null)
{
uint cached = arr[(int)divisor];
if (cached != 0U)
{
return cached;
}
ulong cached = arr[(int)divisor];
if (cached != 0UL)
{
return cached;
}
}
}
@@ -112,7 +112,7 @@ public class MersenneDivisorCycles
}
}
return CalculateCycleLength(divisor);
return CalculateCycleLength(divisor);
}
public static UInt128 GetCycle(UInt128 divisor)
@@ -396,9 +396,9 @@ public class MersenneDivisorCycles
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureSmallBuffer()
{
if (_smallCycles is null)
{
_smallCycles = new uint[PerfectNumberConstants.MaxQForDivisorCycles + 1];
}
}
if (_smallCycles is null)
{
_smallCycles = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
}
}
}