diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 06c85c1..bdfb913 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.Diagnostics; +using System.Globalization; using System.Runtime.CompilerServices; using EvenPerfectBitScanner.Candidates; using EvenPerfectBitScanner.IO; @@ -124,6 +125,8 @@ internal static class Program return; } + Directory.CreateDirectory(PerfectNumberConstants.ByDivisorStateDirectory); + MersenneDivisorCycles mersenneDivisorCycles = new(); if (!File.Exists(cyclesPath) || continueCyclesGeneration) diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs index af1e0cc..2749c3a 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs @@ -48,6 +48,16 @@ public class MersenneNumberDivisorByDivisorTesterTests public ulong MinK { get; set; } + public string? StateFilePath { get; set; } + + public void ResetStateTracking() + { + } + + public void ResumeFromState(ulong lastSavedK) + { + } + public ulong DivisorLimit => ConfiguredMaxPrime + 100UL; public void ConfigureFromMaxPrime(ulong maxPrime) diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index 5075e5b..dc068ec 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -1,28 +1,23 @@ using System.Runtime.CompilerServices; +using System.Globalization; +using System.IO; using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core.Cpu; -public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice orderDevice = ComputationDevice.Hybrid) : IMersenneNumberDivisorByDivisorTester +public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester { private ulong _divisorLimit; private int _batchSize = 1_024; private ulong _minK = 1UL; - - private readonly TryCycleLengthDelegate _tryCalculateCycleLengthForExponent = orderDevice switch - { - ComputationDevice.Gpu => TryCalculateCycleLengthForExponentGpu, - ComputationDevice.Hybrid => TryCalculateCycleLengthForExponentHybrid, - _ => TryCalculateCycleLengthForExponentCpu, - }; - - private readonly CalculateCycleLengthDelegate _calculateCycleLength = orderDevice switch - { - ComputationDevice.Gpu => CalculateCycleLengthGpu, - ComputationDevice.Hybrid => CalculateCycleLengthHybrid, - _ => CalculateCycleLengthCpu, - }; + private readonly ComputationDevice _orderDevice; + private readonly TryCycleLengthDelegate _tryCalculateCycleLengthForExponent; + private readonly CalculateCycleLengthDelegate _calculateCycleLength; + private string? _stateFilePath; + private int _stateCounter; + private ulong _lastSavedK; + public ComputationDevice OrderDevice => _orderDevice; private delegate bool TryCycleLengthDelegate( ulong divisor, @@ -48,6 +43,24 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or public GpuUInt128 Limit; } + public MersenneNumberDivisorByDivisorCpuTester(ComputationDevice orderDevice = ComputationDevice.Hybrid) + { + _orderDevice = orderDevice; + _tryCalculateCycleLengthForExponent = orderDevice switch + { + ComputationDevice.Gpu => TryCalculateCycleLengthForExponentGpu, + ComputationDevice.Hybrid => TryCalculateCycleLengthForExponentHybrid, + _ => TryCalculateCycleLengthForExponentCpu, + }; + + _calculateCycleLength = orderDevice switch + { + ComputationDevice.Gpu => CalculateCycleLengthGpu, + ComputationDevice.Hybrid => CalculateCycleLengthHybrid, + _ => CalculateCycleLengthCpu, + }; + } + public int BatchSize { get => _batchSize; @@ -60,6 +73,24 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or set => _minK = value < 1UL ? 1UL : value; } + public string? StateFilePath + { + get => _stateFilePath; + set => _stateFilePath = value; + } + + public void ResetStateTracking() + { + _stateCounter = 0; + } + + public void ResumeFromState(ulong lastSavedK) + { + _lastSavedK = lastSavedK; + _minK = lastSavedK + 1UL; + _stateCounter = 0; + } + public void ConfigureFromMaxPrime(ulong maxPrime) { _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); @@ -127,11 +158,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or if (session is not null) { - session.Configure(gpu, orderDevice); + session.Configure(gpu, _orderDevice); return session; } - return new MersenneCpuDivisorScanSession(gpu, orderDevice); + return new MersenneCpuDivisorScanSession(gpu, _orderDevice); } private bool CheckDivisors( @@ -144,6 +175,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or { foundDivisor = 0UL; processedAll = true; + ulong currentK = minK < 1UL ? 1UL : minK; // The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs. // Keeping the guard commented out documents the reasoning for benchmarks and tests. @@ -242,6 +274,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or step3, step7, step11, + ref currentK, out processedTop, out foundDivisor); if (composite) @@ -252,9 +285,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or } ulong lowerEnd = startK > 1UL ? startK - 1UL : 0UL; - if (lowerEnd >= 1UL) + if (lowerEnd >= 1UL && startK == 1UL) { lowerEnd = Math.Min(lowerEnd, maxK); + currentK = 1UL; composite = CheckDivisors64Range( gpu, prime, @@ -268,6 +302,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or step3, step7, step11, + ref currentK, out processedBottom, out foundDivisor); } @@ -317,10 +352,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or } else { - divisorCycle = computedCycle; + divisorCycle = computedCycle; } divisorPool.Return(divisorData); + RecordState(currentK); if (divisorCycle == prime) { // A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides @@ -337,6 +373,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or } divisor.Add(step); + currentK++; remainder10 = AddMod10(remainder10, step10); remainder8 = AddMod8(remainder8, step8); remainder3 = AddMod3(remainder3, step3); @@ -362,6 +399,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or byte step3, byte step7, byte step11, + ref ulong currentK, out bool processedAll, out ulong foundDivisor) { @@ -371,6 +409,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or foundDivisor = 0UL; return false; } + currentK = startK; UInt128 step128 = step; UInt128 allowedMax128 = allowedMax; @@ -414,11 +453,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or remainder3, remainder7, remainder11, + ref currentK, out processedAll, out foundDivisor); } - private bool CheckDivisors64( +private bool CheckDivisors64( PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong step, @@ -435,6 +475,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or byte remainder3, byte remainder7, byte remainder11, + ref ulong currentK, out bool processedAll, out ulong foundDivisor) { @@ -523,6 +564,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or } divisorPool.Return(divisorData); + RecordState(currentK); if (divisorCycle == prime) { foundDivisor = divisor; @@ -545,6 +587,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or } divisor += step; + currentK++; remainder10 = AddMod10(remainder10, step10); remainder8 = AddMod8(remainder8, step8); remainder3 = AddMod3(remainder3, step3); @@ -559,6 +602,38 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester(ComputationDevice or return residue == 1UL ? (byte)1 : (byte)0; } + private void RecordState(ulong k) + { + string? path = _stateFilePath; + if (string.IsNullOrEmpty(path)) + { + return; + } + + if (k <= _lastSavedK) + { + return; + } + + int next = _stateCounter + 1; + if (next >= PerfectNumberConstants.ByDivisorStateSaveInterval) + { + string? directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + + File.AppendAllText(path, k.ToString(CultureInfo.InvariantCulture) + Environment.NewLine); + _stateCounter = 0; + _lastSavedK = k; + } + else + { + _stateCounter = next; + } + } + private static bool TryCalculateCycleLengthForExponentCpu( ulong divisor, PrimeOrderCalculatorAccelerator gpu, diff --git a/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs b/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs index 30e79ce..c6a5317 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs @@ -16,10 +16,10 @@ public static class AcceleratorStreamPool public static AcceleratorStream Rent(int acceleratorIndex) { - // var streamLock = _locks[acceleratorIndex]; + var streamLock = _locks[acceleratorIndex]; var queue = _streams[acceleratorIndex]; - // streamLock.Wait(); + streamLock.Wait(); return queue.TryDequeue(out var stream) ? stream @@ -28,10 +28,10 @@ public static class AcceleratorStreamPool public static void Return(int acceleratorIndex, AcceleratorStream stream) { - // var streamLock = _locks[acceleratorIndex]; + var streamLock = _locks[acceleratorIndex]; var queue = _streams[acceleratorIndex]; queue.Enqueue(stream); - // streamLock.Release(); + streamLock.Release(); } public static void WarmUp(int acceleratorIndex) diff --git a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs index 2662674..80c343c 100644 --- a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs +++ b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs @@ -1,12 +1,15 @@ +using PerfectNumbers.Core; + namespace PerfectNumbers.Core.Gpu; public static class GpuPrimeWorkLimiter { - private static SemaphoreSlim _semaphore = new(1, int.MaxValue); + private static readonly TimeSpan _pauseBetweenChecks = TimeSpan.FromSeconds(2); + private static PollingSemaphore _semaphore = new(1, _pauseBetweenChecks); private static int _currentLimit = 1; - public static void Acquire() { } // => _semaphore.Wait(); - public static void Release() { }// => _semaphore.Release(); + public static void Acquire() => _semaphore.Wait(); + public static void Release() => _semaphore.Release(); public static void SetLimit(int value) { @@ -18,13 +21,13 @@ public static class GpuPrimeWorkLimiter if (value == _currentLimit) { return; - } + } // TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using // the same semaphore pool without reallocating per adjustment. - var previous = _semaphore; - _semaphore = new SemaphoreSlim(value, value); + // var previous = _semaphore; + _semaphore = new PollingSemaphore(value, _pauseBetweenChecks); _currentLimit = value; - previous.Dispose(); + // previous.Dispose(); } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 355636a..557fd11 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -43,6 +43,17 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN set => _minK = value < 1UL ? 1UL : value; } + public string? StateFilePath { get; set; } + + public void ResetStateTracking() + { + } + + public void ResumeFromState(ulong lastSavedK) + { + _minK = lastSavedK + 1UL; + } + public void ConfigureFromMaxPrime(ulong maxPrime) { // EvenPerfectBitScanner configures the GPU tester once before scanning and never mutates the configuration afterwards, diff --git a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs index e8edb89..d5437ff 100644 --- a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs @@ -18,6 +18,12 @@ public interface IMersenneNumberDivisorByDivisorTester IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu); + string? StateFilePath { get; set; } + + void ResetStateTracking(); + + void ResumeFromState(ulong lastSavedK); + void PrepareCandidates(in ReadOnlySpan primes, Span allowedMaxValues); public interface IDivisorScanSession diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index de15c2e..3f2597f 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Runtime.InteropServices; using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu.Accelerators; +using PerfectNumbers.Core.Cpu; namespace PerfectNumbers.Core; @@ -240,13 +241,67 @@ public static class MersenneNumberDivisorByDivisorTester int chunkSize = primesPerTask <= 0 ? 1 : primesPerTask; + IMersenneNumberDivisorByDivisorTester CreateTester(IMersenneNumberDivisorByDivisorTester prototype) + { + IMersenneNumberDivisorByDivisorTester tester = prototype switch + { + MersenneNumberDivisorByDivisorCpuTester cpu => new MersenneNumberDivisorByDivisorCpuTester(cpu.OrderDevice) + { + BatchSize = prototype.BatchSize, + MinK = prototype.MinK, + }, + _ => CreateGpuTester(prototype), + }; + + tester.ConfigureFromMaxPrime(maxPrime); + return tester; + } + + static IMersenneNumberDivisorByDivisorTester CreateGpuTester(IMersenneNumberDivisorByDivisorTester prototype) + { + var gpuTester = new MersenneNumberDivisorByDivisorGpuTester + { + MinK = prototype.MinK, + }; + ((IMersenneNumberDivisorByDivisorTester)gpuTester).BatchSize = prototype.BatchSize; + return gpuTester; + } + void ProcessPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime) { Console.WriteLine($"Processing {prime}"); - bool isPrime = tester.IsPrime(gpu, prime, out bool divisorsExhausted, out ulong divisor); + IMersenneNumberDivisorByDivisorTester testerForPrime = CreateTester(tester); + string stateFile = Path.Combine(PerfectNumberConstants.ByDivisorStateDirectory, prime.ToString(CultureInfo.InvariantCulture) + ".bin"); + + ulong resumeK = tester.MinK; + if (File.Exists(stateFile)) + { + ulong lastK = File.ReadLines(stateFile) + .Where(static line => !string.IsNullOrWhiteSpace(line)) + .Select(static line => ulong.Parse(line, NumberStyles.None, CultureInfo.InvariantCulture)) + .Max(); + + resumeK = lastK + 1UL; + testerForPrime.ResumeFromState(lastK); + } + else + { + testerForPrime.ResumeFromState(0UL); + } + + testerForPrime.MinK = resumeK; + testerForPrime.StateFilePath = stateFile; + testerForPrime.ResetStateTracking(); + + bool isPrime = testerForPrime.IsPrime(gpu, prime, out bool divisorsExhausted, out ulong divisor); if (!isPrime) { + if (!string.IsNullOrEmpty(stateFile) && File.Exists(stateFile)) + { + File.Delete(stateFile); + } + markComposite(); printResult(prime, true, true, false, divisor); Console.WriteLine($"Finished processing {prime}"); diff --git a/PerfectNumbers.Core/PerfectNumberConstants.cs b/PerfectNumbers.Core/PerfectNumberConstants.cs index ea33415..40ea78d 100644 --- a/PerfectNumbers.Core/PerfectNumberConstants.cs +++ b/PerfectNumbers.Core/PerfectNumberConstants.cs @@ -15,10 +15,12 @@ public static class PerfectNumberConstants public const int PooledArrayThreshold = 64; public const int Pow2WindowSize = 8; public static int RollingAccelerators = 298; //SharedGpuContext.Device.MaxNumThreadsPerGroup; - public const int ThreadsByAccelerator = 16; + public const int ThreadsByAccelerator = 8; + public const int ByDivisorStateSaveInterval = 10_000; + public const string ByDivisorStateDirectory = "Checks"; // TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal // divisor-cycle datasets we generate offline. // TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune // them automatically when the optimal ranges for 2kp+1 >= 138M change. } - \ No newline at end of file +