From d4dcd0a53440cf44c73bf90ae4cef52fcc72f820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Wed, 24 Sep 2025 19:04:07 +0200 Subject: [PATCH] Implement multi-tier divisor cycle caching and per-thread reuse --- EvenPerfectBitScanner/Program.cs | 114 +++-- .../MersenneNumberDivisorGpuTesterTests.cs | 9 +- .../MersenneNumberOrderGpuTesterTests.cs | 4 +- PerfectNumbers.Core/DivisorCycleCache.cs | 414 ++++++++++++++++++ PerfectNumbers.Core/Gpu/GpuKernelPool.cs | 92 ++-- ...MersenneNumberDivisorByDivisorGpuTester.cs | 31 +- .../Gpu/MersenneNumberDivisorGpuTester.cs | 12 +- PerfectNumbers.Core/MersenneDivisorCycles.cs | 54 +-- 8 files changed, 585 insertions(+), 145 deletions(-) create mode 100644 PerfectNumbers.Core/DivisorCycleCache.cs diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 5006aa5..949d5ae 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -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 hitsSpan = default; - int hitIndex = 0; - int index = 0; + Span 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.Shared.Return(compositesBuffer, clearArray: true); - ArrayPool.Shared.Return(completionsBuffer, clearArray: true); - ArrayPool.Shared.Return(indexBuffer, clearArray: true); - ArrayPool.Shared.Return(primeBuffer, clearArray: true); - ArrayPool.Shared.Return(hitsBuffer, clearArray: true); + } + finally + { + cycleLease.Dispose(); + ArrayPool.Shared.Return(compositesBuffer, clearArray: true); + ArrayPool.Shared.Return(completionsBuffer, clearArray: true); + ArrayPool.Shared.Return(indexBuffer, clearArray: true); + ArrayPool.Shared.Return(primeBuffer, clearArray: true); + ArrayPool.Shared.Return(hitsBuffer, clearArray: true); } }); } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs index aed4d5a..ed8e6f9 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs @@ -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 }); } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberOrderGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberOrderGpuTesterTests.cs index 3c3add5..5f9a69b 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberOrderGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberOrderGpuTesterTests.cs @@ -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(); diff --git a/PerfectNumbers.Core/DivisorCycleCache.cs b/PerfectNumbers.Core/DivisorCycleCache.cs new file mode 100644 index 0000000..dca2f79 --- /dev/null +++ b/PerfectNumbers.Core/DivisorCycleCache.cs @@ -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> _pending = new(); + private CycleBlock _baseBlock = null!; + private CycleBlock? _previous; + private CycleBlock? _current; + private CycleBlock? _next; + private bool _initialized; + + private static readonly Lazy _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? 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? existing)) + { + if (existing.IsCompletedSuccessfully) + { + if (_next is null || _next.Index < blockIndex) + { + _next = existing.Result; + } + + _pending.TryRemove(blockIndex, out _); + } + + return; + } + + Task task = Task.Run(() => GenerateBlock(blockIndex)); + _pending[blockIndex] = task; + task.ContinueWith(t => OnPrefetchCompleted(blockIndex, t), TaskScheduler.Default); + } + + private void OnPrefetchCompleted(int blockIndex, Task 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; + } +} diff --git a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs index 9ee67ef..139a513 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -27,19 +27,19 @@ public sealed class KernelContainer { // Serializes first-time initialization of kernels/buffers per accelerator. public Action, ArrayView>? Order; + public Action, ArrayView1D>? Incremental; public Action, ArrayView1D>? Incremental; - public Action, ArrayView1D, + ResidueAutomatonArgs, ArrayView, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>? Pow2Mod; public Action, ArrayView1D>? IncrementalOrder; + ResidueAutomatonArgs, ArrayView, ArrayView1D>? IncrementalOrder; public Action, ArrayView1D>? Pow2ModOrder; + ResidueAutomatonArgs, ArrayView, ArrayView1D>? Pow2ModOrder; // Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length. - public MemoryBuffer1D? SmallCycles; + public MemoryBuffer1D? SmallCycles; public MemoryBuffer1D? SmallPrimesLastOne; public MemoryBuffer1D? SmallPrimesPow2LastOne; public MemoryBuffer1D? SmallPrimesLastSeven; @@ -123,7 +123,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne } } - public readonly Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> Pow2ModKernel + public readonly Action, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D> 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, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(Pow2ModKernelScan); + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>(Pow2ModKernelScan); var kernel = KernelUtil.GetKernel(loaded); - return kernel.CreateLauncherDelegate, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>>(); + return kernel.CreateLauncherDelegate, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D, ArrayView1D>>(); }); } } } - public readonly Action, ArrayView1D> IncrementalKernel + public readonly Action, ArrayView1D> 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, ArrayView1D>(IncrementalKernelScan); + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalKernelScan); var kernel = KernelUtil.GetKernel(loaded); - return kernel.CreateLauncherDelegate, ArrayView1D>>(); + return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } } } - public readonly Action, ArrayView1D> IncrementalOrderKernel + public readonly Action, ArrayView1D> 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, ArrayView1D>(IncrementalOrderKernelScan); + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(IncrementalOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); - return kernel.CreateLauncherDelegate, ArrayView1D>>(); + return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } } } - public readonly Action, ArrayView1D> Pow2ModOrderKernel + public readonly Action, ArrayView1D> 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, ArrayView1D>(Pow2ModOrderKernelScan); + var loaded = accel.LoadAutoGroupedStreamKernel, ArrayView1D>(Pow2ModOrderKernelScan); var kernel = KernelUtil.GetKernel(loaded); - return kernel.CreateLauncherDelegate, ArrayView1D>>(); + return kernel.CreateLauncherDelegate, ArrayView1D>>(); }); } } @@ -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 orders, - ArrayView1D smallCycles) + ArrayView1D 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 orders, - ArrayView1D smallCycles, + ArrayView1D smallCycles, ArrayView1D smallPrimesLastOne, ArrayView1D smallPrimesLastSeven, ArrayView1D 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 found, ArrayView1D smallCycles) + ResidueAutomatonArgs ra, ArrayView found, ArrayView1D 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 found, ArrayView1D smallCycles) + ResidueAutomatonArgs ra, ArrayView found, ArrayView1D 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 EnsureSmallCyclesOnDevice(Accelerator accelerator) + public static ArrayView1D 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(host.Length); + var device = accelerator.Allocate1D(host.Length); device.View.CopyFromCPU(host); kernels.SmallCycles = device; return device.View; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 0274b82..9762ff9 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -389,12 +389,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester _positionBuffer = ArrayPool.Shared.Rent(_capacity); } - public void CheckDivisor(ulong divisor, ReadOnlySpan primes, Span hits) - { - if (_disposed) - { - throw new ObjectDisposedException(nameof(DivisorScanSession)); - } + public void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan primes, Span 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, ArrayView> 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; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs index 14a666e..289c442 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs @@ -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]; diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 2666105..7a55761 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -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]; + } + } }