Refine by-divisor prime test time limits

This commit is contained in:
Marek Lemańczyk committed 2025-10-11 22:28:52 +02:00
1 parent b47e758235
commit 6a1925dd47
9 files changed
+260 -71

No files matched your search

+62 -3
View File
@@ -44,6 +44,7 @@ internal static class Program
private static string? _resultsPrefix;
private static readonly Optimized PrimeIterator = new();
private static ulong _byDivisorStartPrime;
private static TimeSpan? _primeTestLimit;
[ThreadStatic]
private static bool _lastCompositeP;
@@ -181,6 +182,17 @@ internal static class Program
{
mersenneOnGpu = !arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
}
else if (arg.StartsWith("--prime-test-limit=", StringComparison.OrdinalIgnoreCase))
{
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
if (!TryParsePrimeTestLimit(value, out TimeSpan parsedLimitValue))
{
Console.WriteLine("Failed to parse --prime-test-limit value.");
return;
}
_primeTestLimit = parsedLimitValue;
}
else if (arg.Equals("--workaround-mod", StringComparison.OrdinalIgnoreCase))
{
useModuloWorkaround = true;
@@ -667,7 +679,7 @@ internal static class Program
if (_useByDivisorMode)
{
RunByDivisorMode(byDivisorCandidates, threadCount);
RunByDivisorMode(byDivisorCandidates, threadCount, _primeTestLimit);
FlushBuffer();
StringBuilderPool.Return(_outputBuilder!);
return;
@@ -808,7 +820,7 @@ internal static class Program
return candidates;
}
private static void RunByDivisorMode(List<ulong> candidates, int threadCount)
private static void RunByDivisorMode(List<ulong> candidates, int threadCount, TimeSpan? primeTestLimit)
{
if (_byDivisorTester is null)
{
@@ -825,7 +837,8 @@ internal static class Program
static () => _lastCompositeP = true,
static () => _lastCompositeP = false,
PrintResult,
threadCount);
threadCount,
primeTestLimit);
}
private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction)
@@ -892,6 +905,51 @@ internal static class Program
}
}
private static bool TryParsePrimeTestLimit(ReadOnlySpan<char> value, out TimeSpan result)
{
result = TimeSpan.Zero;
if (value.IsEmpty)
{
return false;
}
ReadOnlySpan<char> magnitudeSpan = value;
double magnitude;
if (value.Length >= 2 && value.EndsWith("ms", StringComparison.OrdinalIgnoreCase))
{
magnitudeSpan = value[..^2];
if (!double.TryParse(magnitudeSpan, NumberStyles.Float, CultureInfo.InvariantCulture, out magnitude))
{
return false;
}
result = TimeSpan.FromMilliseconds(magnitude);
return true;
}
char suffix = char.ToLowerInvariant(value[^1]);
if (suffix == 's' || suffix == 'm')
{
magnitudeSpan = value[..^1];
if (!double.TryParse(magnitudeSpan, NumberStyles.Float, CultureInfo.InvariantCulture, out magnitude))
{
return false;
}
result = suffix == 's' ? TimeSpan.FromSeconds(magnitude) : TimeSpan.FromMinutes(magnitude);
return true;
}
if (!double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out magnitude))
{
return false;
}
result = TimeSpan.FromMilliseconds(magnitude);
return true;
}
private static void PrintHelp()
{
Console.WriteLine("Usage: EvenPerfectBitScanner [options]");
@@ -925,6 +983,7 @@ internal static class Program
Console.WriteLine(" --divisor-cycles-batch=<value> batch size for cycles generation (default 512)");
Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation");
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor");
Console.WriteLine(" --prime-test-limit=<value> Time limit for --mersenne=bydivisor prime checks (e.g. 5s, 500ms)");
Console.WriteLine(" --use-order test primality via q order");
Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU");
// mod-automaton removed
@@ -26,7 +26,8 @@ public class MersenneNumberDivisorByDivisorTesterTests
markComposite: () => compositeMarks++,
clearComposite: () => clearedMarks++,
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
threadCount: 1);
threadCount: 1,
primeTestLimit: null);
tester.ConfiguredMaxPrime.Should().Be(13UL);
compositeMarks.Should().Be(2);
@@ -59,7 +60,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
return DivisorLimit;
}
public bool IsPrime(ulong prime, out bool divisorsExhausted)
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
{
divisorsExhausted = true;
return prime % 7UL != 0UL && prime % 11UL != 0UL;
@@ -7,78 +7,88 @@ namespace PerfectNumbers.Core.Tests;
public class MersenneNumberDivisorCpuTesterTests
{
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_tracks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(11UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_tracks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(11UL);
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeTrue();
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
}
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue();
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_checks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(13UL);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_prime_test_limit_disables_divisor_exhaustion()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(11UL);
using var session = tester.CreateDivisorSession();
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
byte[] hits = new byte[primes.Length];
tester.IsPrime(5UL, out bool divisorsExhausted, TimeSpan.Zero).Should().BeTrue();
divisorsExhausted.Should().BeFalse();
}
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_checks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(13UL);
Array.Fill(hits, (byte)0);
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
}
using var session = tester.CreateDivisorSession();
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
byte[] hits = new byte[primes.Length];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(43UL);
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
using var session = tester.CreateDivisorSession();
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
byte[] hits = new byte[exponents.Length];
Array.Fill(hits, (byte)0);
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
}
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(43UL);
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
using var session = tester.CreateDivisorSession();
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
byte[] hits = new byte[exponents.Length];
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(61UL);
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
using var session = tester.CreateDivisorSession();
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
byte[] hits = new byte[exponents.Length];
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
{
var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(61UL);
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
using var session = tester.CreateDivisorSession();
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
byte[] hits = new byte[exponents.Length];
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
}
}
@@ -86,6 +86,17 @@ public class MersenneNumberDivisorGpuTesterTests
exhausted.Should().BeTrue();
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_gpu_prime_test_limit_stops_scan_immediately()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL);
tester.IsPrime(31UL, out bool divisorsExhausted, TimeSpan.Zero).Should().BeTrue();
divisorsExhausted.Should().BeFalse();
}
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_tester_requires_configuration()
@@ -61,7 +61,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
}
}
public bool IsPrime(ulong prime, out bool divisorsExhausted)
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
{
ulong allowedMax;
lock (_sync)
@@ -89,7 +89,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
allowedMax,
out lastProcessed,
out processedAll,
out processedCount);
out processedCount,
timeLimit);
if (processedCount > 0UL)
{
@@ -163,12 +164,22 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
ulong allowedMax,
out ulong lastProcessed,
out bool processedAll,
out ulong processedCount)
out ulong processedCount,
TimeSpan? timeLimit)
{
lastProcessed = 0UL;
processedCount = 0UL;
processedAll = false;
PrimeTestTimeLimit limitGuard;
if (!PrimeTestTimeLimit.TryCreate(timeLimit, out limitGuard))
{
return false;
}
bool enforceLimit = limitGuard.IsActive;
ulong iterationCounter = 0UL;
if (allowedMax < 3UL)
{
return false;
@@ -216,6 +227,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
while (divisor <= limit)
{
if (enforceLimit && (iterationCounter == 0UL || (iterationCounter & 63UL) == 0UL) && limitGuard.HasExpired())
{
processedAll = false;
return false;
}
iterationCounter++;
ulong candidate = (ulong)divisor;
processedCount++;
lastProcessed = candidate;
@@ -71,7 +71,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
}
}
public bool IsPrime(ulong prime, out bool divisorsExhausted)
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
{
ulong allowedMax;
int batchCapacity;
@@ -108,6 +108,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
composite = CheckDivisors(
prime,
allowedMax,
timeLimit,
accelerator,
kernel,
resources.DivisorsBuffer,
@@ -175,6 +176,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private static bool CheckDivisors(
ulong prime,
ulong allowedMax,
TimeSpan? timeLimit,
Accelerator accelerator,
Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> kernel,
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
@@ -202,6 +204,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
bool processedAll = false;
processedCount = 0UL;
lastProcessed = 0UL;
coveredRange = false;
PrimeTestTimeLimit limitGuard;
if (!PrimeTestTimeLimit.TryCreate(timeLimit, out limitGuard))
{
return false;
}
bool enforceLimit = limitGuard.IsActive;
Span<MontgomeryDivisorData> divisorDataSpan;
Span<ulong> divisorSpan;
@@ -277,6 +288,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
{
while (currentK <= maxK)
{
if (enforceLimit && limitGuard.HasExpired())
{
processedAll = false;
return false;
}
int chunkCount = Math.Min(chunkCapacity, batchCapacity);
ulong remainingK = maxK - currentK + 1UL;
if ((ulong)chunkCount > remainingK)
@@ -12,7 +12,7 @@ public interface IMersenneNumberDivisorByDivisorTester
ulong GetAllowedMaxDivisor(ulong prime);
bool IsPrime(ulong prime, out bool divisorsExhausted);
bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null);
IDivisorScanSession CreateDivisorSession();
@@ -18,7 +18,8 @@ public static class MersenneNumberDivisorByDivisorTester
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool> printResult,
int threadCount)
int threadCount,
TimeSpan? primeTestLimit)
{
if (candidates.Count == 0)
{
@@ -243,7 +244,7 @@ public static class MersenneNumberDivisorByDivisorTester
void ProcessPrime(ulong prime)
{
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted, primeTestLimit);
if (!isPrime)
{
+72
View File
@@ -0,0 +1,72 @@
using System;
using System.Diagnostics;
namespace PerfectNumbers.Core;
internal readonly struct PrimeTestTimeLimit
{
private readonly long _deadlineTimestamp;
private PrimeTestTimeLimit(bool isActive, long deadlineTimestamp)
{
IsActive = isActive;
_deadlineTimestamp = deadlineTimestamp;
}
internal bool IsActive { get; }
internal static bool TryCreate(TimeSpan? timeLimit, out PrimeTestTimeLimit limit)
{
if (!timeLimit.HasValue)
{
limit = new PrimeTestTimeLimit(isActive: false, deadlineTimestamp: long.MaxValue);
return true;
}
TimeSpan limitValue = timeLimit.GetValueOrDefault();
if (limitValue <= TimeSpan.Zero)
{
limit = new PrimeTestTimeLimit(isActive: true, deadlineTimestamp: long.MinValue);
return false;
}
double seconds = limitValue.TotalSeconds;
if (double.IsInfinity(seconds))
{
limit = new PrimeTestTimeLimit(isActive: true, deadlineTimestamp: long.MaxValue);
return true;
}
double scaled = seconds * Stopwatch.Frequency;
long limitTicks = scaled >= long.MaxValue ? long.MaxValue : (long)scaled;
long startTimestamp = Stopwatch.GetTimestamp();
long deadline = startTimestamp <= long.MaxValue - limitTicks ? startTimestamp + limitTicks : long.MaxValue;
if (deadline < startTimestamp)
{
deadline = long.MaxValue;
}
limit = new PrimeTestTimeLimit(isActive: true, deadlineTimestamp: deadline);
return true;
}
internal bool HasExpired()
{
if (!IsActive)
{
return false;
}
if (_deadlineTimestamp == long.MaxValue)
{
return false;
}
if (_deadlineTimestamp == long.MinValue)
{
return true;
}
return Stopwatch.GetTimestamp() >= _deadlineTimestamp;
}
}