Tasks will now save calculations progress and resume calculation upon restart.

This commit is contained in:
mlemanczyk committed 2025-12-02 00:42:38 +01:00
1 parent 941733905b
commit f43c67c86f
9 files changed
+199 -34

No files matched your search

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