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
+363 -11

No files matched your search

+213 -11
View File
@@ -1,3 +1,4 @@
using System.Buffers;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -11,8 +12,8 @@ namespace EvenPerfectBitScanner;
internal static class Program
{
private static ThreadLocal<PrimeTester> PrimeTesters = null!;
private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!;
private static ThreadLocal<PrimeTester> PrimeTesters = null!;
private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!;
private static ThreadLocal<ModResidueTracker> PResidue = null!; // p mod d tracker (per-thread)
private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP;
@@ -31,6 +32,7 @@ internal static class Program
private static bool _useDivisor;
private static bool _useResidueMode;
private static bool _useByDivisorMode;
private static bool _byDivisorPrecheckOnly;
private static UInt128 _divisor;
private static MersenneNumberDivisorGpuTester? _divisorTester;
private static MersenneNumberDivisorByDivisorGpuTester? _byDivisorTester;
@@ -54,9 +56,18 @@ internal static class Program
private static double _zeroFracConj = -1.0; // disabled when < 0
private static int _maxZeroConj = -1; // disabled when < 0
private static unsafe void Main(string[] args)
{
ulong currentP = InitialP;
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)
{
ulong currentP = InitialP;
int threadCount = Environment.ProcessorCount;
int blockSize = 1;
int gpuPrimeThreads = 1;
@@ -577,6 +588,7 @@ internal static class Program
bool useFilter = !string.IsNullOrEmpty(filterFile);
HashSet<ulong> filter = [];
List<ulong> filterList = [];
ulong maxP = 0UL;
if (useFilter)
{
@@ -595,7 +607,7 @@ internal static class Program
if (count == 1024)
{
filter.AddRange(localFilter[..count]);
filter.AddRange(localFilter[..count]);
count = 0;
Console.WriteLine($"Added {p}");
}
@@ -604,10 +616,9 @@ internal static class Program
if (count > 0)
{
filter.AddRange(localFilter[..count]);
filter.AddRange(localFilter[..count]);
}
// Restore this if you want to use List<ulong> instead of HashSet<ulong>
// filter.Sort();
filterList = [..filter];
}
if (useByDivisor)
@@ -633,6 +644,14 @@ internal static class Program
// Configure batch size for GPU primality sieve
PrimeTester.GpuBatchSize = gpuPrimeBatch;
if (_useByDivisorMode)
{
RunByDivisorMode(filterList, divisorCyclesSearchLimit);
FlushBuffer();
StringBuilderPool.Return(_outputBuilder!);
return;
}
if (!useDivisor && !useByDivisor)
{
Console.WriteLine("Warming up orders...");
@@ -713,8 +732,184 @@ internal static class Program
Task.WaitAll(tasks);
FlushBuffer();
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)
{
@@ -1163,6 +1358,13 @@ internal static class Program
searchedMersenne = true;
if (_useByDivisorMode)
{
if (_byDivisorPrecheckOnly)
{
searchedMersenne = false;
detailedCheck = false;
return true;
}
return _byDivisorTester!.IsPrime(p, out detailedCheck);
}
@@ -103,5 +103,23 @@ public class MersenneNumberDivisorGpuTesterTests
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 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) =>
_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
{
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)
{
lock (_sync)
@@ -179,6 +225,79 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
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)
{
if (divisor <= _lastStatusDivisor)
@@ -234,6 +353,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester
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)
{
if (modulus <= 1UL)