Rework by-divisor scanning order

This commit is contained in:
Marek Lemańczyk committed 2025-09-21 13:00:17 +02:00
1 parent 9a054542e6
commit f91f526121
3 files changed
+354 -2

No files matched your search

+204 -2
View File
@@ -1,3 +1,4 @@
using System.Buffers;
using System.Globalization; using System.Globalization;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -31,6 +32,7 @@ internal static class Program
private static bool _useDivisor; private static bool _useDivisor;
private static bool _useResidueMode; private static bool _useResidueMode;
private static bool _useByDivisorMode; private static bool _useByDivisorMode;
private static bool _byDivisorPrecheckOnly;
private static UInt128 _divisor; private static UInt128 _divisor;
private static MersenneNumberDivisorGpuTester? _divisorTester; private static MersenneNumberDivisorGpuTester? _divisorTester;
private static MersenneNumberDivisorByDivisorGpuTester? _byDivisorTester; private static MersenneNumberDivisorByDivisorGpuTester? _byDivisorTester;
@@ -54,6 +56,15 @@ internal static class Program
private static double _zeroFracConj = -1.0; // disabled when < 0 private static double _zeroFracConj = -1.0; // disabled when < 0
private static int _maxZeroConj = -1; // disabled when < 0 private static int _maxZeroConj = -1; // disabled when < 0
private struct ByDivisorPrimeState
{
internal ulong Prime;
internal ulong AllowedMax;
internal bool Completed;
internal bool Composite;
internal bool DetailedCheck;
}
private static unsafe void Main(string[] args) private static unsafe void Main(string[] args)
{ {
ulong currentP = InitialP; ulong currentP = InitialP;
@@ -577,6 +588,7 @@ internal static class Program
bool useFilter = !string.IsNullOrEmpty(filterFile); bool useFilter = !string.IsNullOrEmpty(filterFile);
HashSet<ulong> filter = []; HashSet<ulong> filter = [];
List<ulong> filterList = [];
ulong maxP = 0UL; ulong maxP = 0UL;
if (useFilter) if (useFilter)
{ {
@@ -606,8 +618,7 @@ internal static class Program
{ {
filter.AddRange(localFilter[..count]); filter.AddRange(localFilter[..count]);
} }
// Restore this if you want to use List<ulong> instead of HashSet<ulong> filterList = [..filter];
// filter.Sort();
} }
if (useByDivisor) if (useByDivisor)
@@ -633,6 +644,14 @@ internal static class Program
// Configure batch size for GPU primality sieve // Configure batch size for GPU primality sieve
PrimeTester.GpuBatchSize = gpuPrimeBatch; PrimeTester.GpuBatchSize = gpuPrimeBatch;
if (_useByDivisorMode)
{
RunByDivisorMode(filterList, divisorCyclesSearchLimit);
FlushBuffer();
StringBuilderPool.Return(_outputBuilder!);
return;
}
if (!useDivisor && !useByDivisor) if (!useDivisor && !useByDivisor)
{ {
Console.WriteLine("Warming up orders..."); Console.WriteLine("Warming up orders...");
@@ -716,6 +735,182 @@ internal static class Program
StringBuilderPool.Return(_outputBuilder!); StringBuilderPool.Return(_outputBuilder!);
} }
private static void RunByDivisorMode(List<ulong> primes, ulong divisorCyclesSearchLimit)
{
if (primes.Count == 0)
{
return;
}
primes.Sort();
List<ByDivisorPrimeState> states = new(primes.Count);
_byDivisorPrecheckOnly = true;
foreach (ulong prime in primes)
{
bool passed = IsEvenPerfectCandidate(prime, divisorCyclesSearchLimit, out bool searched, out bool detailed);
if (!passed)
{
PrintResult(prime, searched, detailed, false);
continue;
}
ulong allowedMax = _byDivisorTester!.GetAllowedMaxDivisor(prime);
if (allowedMax < 3UL)
{
PrintResult(prime, searchedMersenne: true, detailedCheck: true, passedAllTests: true);
continue;
}
states.Add(new ByDivisorPrimeState
{
Prime = prime,
AllowedMax = allowedMax,
Completed = false,
Composite = false,
DetailedCheck = false,
});
}
_byDivisorPrecheckOnly = false;
if (states.Count == 0)
{
return;
}
using var session = _byDivisorTester!.CreateDivisorSession();
byte[] hitsBuffer = ArrayPool<byte>.Shared.Rent(states.Count);
ulong[] primeBuffer = ArrayPool<ulong>.Shared.Rent(states.Count);
int[] indexBuffer = ArrayPool<int>.Shared.Rent(states.Count);
try
{
ulong divisor = 3UL;
ulong divisorLimit = _byDivisorTester!.DivisorLimit;
while (true)
{
bool anyPending = false;
int activeCount = 0;
for (int i = 0; i < states.Count; i++)
{
var state = states[i];
if (state.Completed)
{
continue;
}
if (state.AllowedMax < divisor)
{
state.Completed = true;
state.DetailedCheck = true;
states[i] = state;
PrintResult(state.Prime, searchedMersenne: true, detailedCheck: true, passedAllTests: true);
continue;
}
anyPending = true;
if (divisor > divisorLimit)
{
states[i] = state;
continue;
}
primeBuffer[activeCount] = state.Prime;
indexBuffer[activeCount] = i;
activeCount++;
states[i] = state;
}
if (!anyPending || divisor > divisorLimit)
{
break;
}
if (activeCount == 0)
{
ulong nextDivisor = divisor + 2UL;
if (nextDivisor <= divisor)
{
break;
}
divisor = nextDivisor;
continue;
}
session.CheckDivisor(divisor, primeBuffer.AsSpan(0, activeCount), hitsBuffer.AsSpan(0, activeCount));
for (int i = 0; i < activeCount; i++)
{
if (hitsBuffer[i] == 0)
{
continue;
}
int index = indexBuffer[i];
var state = states[index];
if (state.Completed)
{
continue;
}
state.Completed = true;
state.Composite = true;
state.DetailedCheck = true;
states[index] = state;
PrintResult(state.Prime, searchedMersenne: true, detailedCheck: true, passedAllTests: false);
}
bool remaining = false;
for (int i = 0; i < states.Count; i++)
{
if (!states[i].Completed)
{
remaining = true;
break;
}
}
if (!remaining)
{
break;
}
ulong next = divisor + 2UL;
if (next <= divisor)
{
break;
}
divisor = next;
}
for (int i = 0; i < states.Count; i++)
{
var state = states[i];
if (state.Completed)
{
continue;
}
state.Completed = true;
state.DetailedCheck = divisor > state.AllowedMax;
states[i] = state;
PrintResult(state.Prime, searchedMersenne: true, detailedCheck: state.DetailedCheck, passedAllTests: true);
}
}
finally
{
ArrayPool<int>.Shared.Return(indexBuffer, clearArray: true);
ArrayPool<ulong>.Shared.Return(primeBuffer, clearArray: true);
ArrayPool<byte>.Shared.Return(hitsBuffer, clearArray: true);
}
}
private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction) private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction)
{ {
using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
@@ -1163,6 +1358,13 @@ internal static class Program
searchedMersenne = true; searchedMersenne = true;
if (_useByDivisorMode) if (_useByDivisorMode)
{ {
if (_byDivisorPrecheckOnly)
{
searchedMersenne = false;
detailedCheck = false;
return true;
}
return _byDivisorTester!.IsPrime(p, out detailedCheck); return _byDivisorTester!.IsPrime(p, out detailedCheck);
} }
@@ -103,5 +103,23 @@ public class MersenneNumberDivisorGpuTesterTests
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
} }
[Fact]
[Trait("Category", "Fast")]
public void ByDivisor_session_checks_divisors_across_primes()
{
var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(13UL);
using var session = tester.CreateDivisorSession();
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
byte[] hits = new byte[primes.Length];
session.CheckDivisor(23UL, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
Array.Fill(hits, (byte)0);
session.CheckDivisor(31UL, primes, hits);
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
}
} }
@@ -17,10 +17,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
private ulong _lastStatusDivisor; private ulong _lastStatusDivisor;
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>>> _kernelCache = new(); private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>>> _kernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>>> _kernelByPrimeCache = new();
private Action<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>> GetKernel(Accelerator accelerator) => private Action<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>>(CheckKernel)); _kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>>(CheckKernel));
private Action<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>> GetKernelByPrime(Accelerator accelerator) =>
_kernelByPrimeCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>>(CheckKernelByPrime));
public int GpuBatchSize public int GpuBatchSize
{ {
get => _gpuBatchSize; get => _gpuBatchSize;
@@ -37,6 +41,48 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
} }
} }
public ulong DivisorLimit
{
get
{
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
return _divisorLimit;
}
}
}
public ulong GetAllowedMaxDivisor(ulong prime)
{
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
return ComputeAllowedMaxDivisor(prime);
}
}
public DivisorScanSession CreateDivisorSession()
{
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
return new DivisorScanSession(this);
}
}
public bool IsPrime(ulong prime, out bool divisorsExhausted) public bool IsPrime(ulong prime, out bool divisorsExhausted)
{ {
lock (_sync) lock (_sync)
@@ -179,6 +225,79 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
return composite; return composite;
} }
public sealed class DivisorScanSession : IDisposable
{
private readonly MersenneNumberDivisorByDivisorGpuTester _owner;
private readonly GpuContextPool.GpuContextLease _lease;
private readonly Accelerator _accelerator;
private readonly Action<Index1D, ulong, ArrayView<ulong>, ArrayView<byte>> _kernel;
private readonly MemoryBuffer1D<ulong, Stride1D.Dense> _primesBuffer;
private readonly MemoryBuffer1D<byte, Stride1D.Dense> _hitsBuffer;
private readonly ulong[] _primesHost;
private readonly byte[] _hitsHost;
private bool _disposed;
internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner)
{
_owner = owner;
_lease = GpuContextPool.RentPreferred(preferCpu: false);
_accelerator = _lease.Accelerator;
_kernel = owner.GetKernelByPrime(_accelerator);
_primesBuffer = _accelerator.Allocate1D<ulong>(owner._gpuBatchSize);
_hitsBuffer = _accelerator.Allocate1D<byte>(owner._gpuBatchSize);
_primesHost = ArrayPool<ulong>.Shared.Rent(owner._gpuBatchSize);
_hitsHost = ArrayPool<byte>.Shared.Rent(owner._gpuBatchSize);
}
public void CheckDivisor(ulong divisor, ReadOnlySpan<ulong> primes, Span<byte> hits)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DivisorScanSession));
}
if (primes.Length == 0)
{
return;
}
int offset = 0;
while (offset < primes.Length)
{
int batchSize = Math.Min(_owner._gpuBatchSize, primes.Length - offset);
Span<ulong> primeSpan = _primesHost.AsSpan(0, batchSize);
primes.Slice(offset, batchSize).CopyTo(primeSpan);
var primesView = _primesBuffer.View.SubView(0, batchSize);
var hitsView = _hitsBuffer.View.SubView(0, batchSize);
primesView.CopyFromCPU(ref MemoryMarshal.GetReference(primeSpan), batchSize);
_kernel(batchSize, divisor, primesView, hitsView);
_accelerator.Synchronize();
Span<byte> hitSpan = _hitsHost.AsSpan(0, batchSize);
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitSpan), batchSize);
hitSpan.CopyTo(hits.Slice(offset, batchSize));
offset += batchSize;
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
ArrayPool<ulong>.Shared.Return(_primesHost, clearArray: true);
ArrayPool<byte>.Shared.Return(_hitsHost, clearArray: true);
_primesBuffer.Dispose();
_hitsBuffer.Dispose();
_lease.Dispose();
}
}
private void ReportStatus(ulong divisor) private void ReportStatus(ulong divisor)
{ {
if (divisor <= _lastStatusDivisor) if (divisor <= _lastStatusDivisor)
@@ -234,6 +353,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
hits[index] = pow == 1UL ? (byte)1 : (byte)0; hits[index] = pow == 1UL ? (byte)1 : (byte)0;
} }
private static void CheckKernelByPrime(Index1D index, ulong divisor, ArrayView<ulong> primes, ArrayView<byte> hits)
{
ulong prime = primes[index];
if (divisor <= 1UL || (divisor & 1UL) == 0UL)
{
hits[index] = 0;
return;
}
ulong pow = Pow2Mod(prime, divisor);
hits[index] = pow == 1UL ? (byte)1 : (byte)0;
}
private static ulong Pow2Mod(ulong exponent, ulong modulus) private static ulong Pow2Mod(ulong exponent, ulong modulus)
{ {
if (modulus <= 1UL) if (modulus <= 1UL)