Merge pull request #200 from mlemanczyk/codex/implement-todos-in-evenperfectbitscanner
Move prime tracking into CalculationResultHandler
This commit is contained in:
7 files changed
+1274
-560
No files matched your search
@@ -0,0 +1,377 @@
|
||||
using System.Buffers.Text;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner;
|
||||
|
||||
internal struct CalculationResultHandler
|
||||
{
|
||||
private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
||||
internal const int DefaultWriteBatchSize = 100;
|
||||
|
||||
private static readonly UTF8Encoding Utf8Encoding = new(false, true);
|
||||
|
||||
private readonly object _sync;
|
||||
private readonly object _fileWriteSync;
|
||||
private StringBuilder? _outputBuilder;
|
||||
private TextFileWriter? _resultsWriter;
|
||||
private int _consoleCounter;
|
||||
private int _writeIndex;
|
||||
private readonly int _writeBatchSize;
|
||||
private readonly string _resultsFileName;
|
||||
private int _primeCount;
|
||||
private bool _primeFoundAfterInit;
|
||||
|
||||
private bool IsInitialized => _sync is not null;
|
||||
private bool HasFileSync => _fileWriteSync is not null;
|
||||
|
||||
internal CalculationResultHandler(string resultsFileName, int writeBatchSize)
|
||||
{
|
||||
_sync = new object();
|
||||
_fileWriteSync = new object();
|
||||
_outputBuilder = null;
|
||||
_resultsWriter = null;
|
||||
_consoleCounter = 0;
|
||||
_writeIndex = 0;
|
||||
_writeBatchSize = writeBatchSize > 0 ? writeBatchSize : DefaultWriteBatchSize;
|
||||
_resultsFileName = resultsFileName;
|
||||
_primeCount = 0;
|
||||
_primeFoundAfterInit = false;
|
||||
}
|
||||
|
||||
internal string ResultsFileName => _resultsFileName;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void InitializeOutputBuffer()
|
||||
{
|
||||
// Program flow ensures the handler is initialized before this call. Do not uncomment the guard below.
|
||||
// if (!IsInitialized)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void ReleaseOutputBuffer()
|
||||
{
|
||||
// Program flow ensures the handler is initialized before this call. Do not uncomment the guard below.
|
||||
// if (!IsInitialized)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
StringBuilder builder = _outputBuilder!;
|
||||
// Program flow guarantees the output builder is populated when releasing. Do not uncomment the guard below.
|
||||
// if (builder is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
_outputBuilder = null;
|
||||
_writeIndex = 0;
|
||||
StringBuilderPool.Return(builder);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void CreateResultsFileWithHeader()
|
||||
{
|
||||
// Program flow guarantees the file synchronization primitives are available here. Do not uncomment the guard below.
|
||||
// if (!HasFileSync)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
lock (_fileWriteSync)
|
||||
{
|
||||
_resultsWriter?.Dispose();
|
||||
var writer = new TextFileWriter(_resultsFileName, FileMode.Create);
|
||||
writer.WriteLine(ResultsHeader.AsSpan());
|
||||
_resultsWriter = writer;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RegisterExistingResult(bool detailedCheck, bool passedAllTests)
|
||||
{
|
||||
if (!detailedCheck || !passedAllTests)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int newCount = _primeCount + 1;
|
||||
_primeCount = newCount;
|
||||
if (newCount >= 2)
|
||||
{
|
||||
_primeFoundAfterInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
||||
{
|
||||
// Program flow ensures the handler is initialized before this call. Do not uncomment the guard below.
|
||||
// if (!IsInitialized)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
Span<byte> stackBuffer = stackalloc byte[128];
|
||||
byte[]? rentedBuffer = null;
|
||||
Span<byte> utf8Span = stackBuffer;
|
||||
int byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
|
||||
if (byteCount < 0)
|
||||
{
|
||||
rentedBuffer = ThreadStaticPools.BytePool.Rent(256);
|
||||
utf8Span = rentedBuffer;
|
||||
byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
|
||||
if (byteCount < 0)
|
||||
{
|
||||
ThreadStaticPools.BytePool.Return(rentedBuffer);
|
||||
throw new InvalidOperationException("Result buffer too small.");
|
||||
}
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> utf8Line = utf8Span[..byteCount];
|
||||
Span<char> charBuffer = stackalloc char[128];
|
||||
int charCount = Utf8Encoding.GetChars(utf8Line, charBuffer);
|
||||
ReadOnlySpan<char> recordSpan = charBuffer[..charCount];
|
||||
|
||||
bool primeCandidate = UpdatePrimeTracking(passedAllTests, lastWasComposite);
|
||||
bool printToConsole = TryAcquireConsoleSlot(primeCandidate, out bool primeFlag);
|
||||
StringBuilder? builderToFlush = AppendToOutputBuffer(recordSpan);
|
||||
|
||||
if (builderToFlush is not null)
|
||||
{
|
||||
FlushBuffer(builderToFlush);
|
||||
}
|
||||
|
||||
if (printToConsole)
|
||||
{
|
||||
ReadOnlySpan<char> consoleSpan = recordSpan[..^1];
|
||||
TextWriter consoleWriter = Console.Out;
|
||||
if (primeFlag)
|
||||
{
|
||||
consoleWriter.Write(consoleSpan);
|
||||
consoleWriter.Write(PrimeFoundSuffix);
|
||||
consoleWriter.WriteLine();
|
||||
}
|
||||
else
|
||||
{
|
||||
consoleWriter.WriteLine(consoleSpan);
|
||||
}
|
||||
}
|
||||
|
||||
if (rentedBuffer is not null)
|
||||
{
|
||||
ThreadStaticPools.BytePool.Return(rentedBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void FlushBuffer()
|
||||
{
|
||||
// Program flow ensures the handler is initialized before this call. Do not uncomment the guard below.
|
||||
// if (!IsInitialized)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
StringBuilder? builderToFlush;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_writeIndex == 0 || _outputBuilder is null || _outputBuilder.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builderToFlush = _outputBuilder;
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
|
||||
FlushBuffer(builderToFlush!);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Dispose()
|
||||
{
|
||||
// Program flow guarantees the file synchronization primitives are available here. Do not uncomment the guard below.
|
||||
// if (!HasFileSync)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
lock (_fileWriteSync)
|
||||
{
|
||||
_resultsWriter?.Dispose();
|
||||
_resultsWriter = null;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool TryAcquireConsoleSlot(bool primeCandidate, out bool primeFlag)
|
||||
{
|
||||
int consoleInterval = PerfectNumberConstants.ConsoleInterval;
|
||||
int counterValue = Interlocked.Increment(ref _consoleCounter);
|
||||
if (counterValue >= consoleInterval)
|
||||
{
|
||||
int previous = Interlocked.Exchange(ref _consoleCounter, 0);
|
||||
if (previous >= consoleInterval)
|
||||
{
|
||||
primeFlag = primeCandidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
primeFlag = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private StringBuilder? AppendToOutputBuffer(ReadOnlySpan<char> text)
|
||||
{
|
||||
StringBuilder? builderToFlush = null;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
StringBuilder outputBuilder = _outputBuilder!;
|
||||
_ = outputBuilder.Append(text);
|
||||
|
||||
int nextIndex = _writeIndex + 1;
|
||||
if (nextIndex >= _writeBatchSize)
|
||||
{
|
||||
builderToFlush = outputBuilder;
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_writeIndex = nextIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return builderToFlush;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
if (!Utf8Formatter.TryFormat(currentP, destination[offset..], out int written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)',';
|
||||
|
||||
if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)',';
|
||||
|
||||
if (!Utf8Formatter.TryFormat(detailedCheck, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)',';
|
||||
|
||||
if (!Utf8Formatter.TryFormat(passedAllTests, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)'\n';
|
||||
return offset;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void FlushBuffer(StringBuilder builderToFlush)
|
||||
{
|
||||
// Program flow guarantees the file synchronization primitives are available here. Do not uncomment the guard below.
|
||||
// if (!HasFileSync)
|
||||
// {
|
||||
// builderToFlush.Clear();
|
||||
// StringBuilderPool.Return(builderToFlush);
|
||||
// return;
|
||||
// }
|
||||
|
||||
try
|
||||
{
|
||||
lock (_fileWriteSync)
|
||||
{
|
||||
TextFileWriter writer = EnsureResultsWriter();
|
||||
writer.Write(builderToFlush);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
builderToFlush.Clear();
|
||||
StringBuilderPool.Return(builderToFlush);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private TextFileWriter EnsureResultsWriter()
|
||||
{
|
||||
TextFileWriter? writer = _resultsWriter;
|
||||
if (writer is not null)
|
||||
{
|
||||
return writer;
|
||||
}
|
||||
|
||||
writer = new TextFileWriter(_resultsFileName, FileMode.Append);
|
||||
_resultsWriter = writer;
|
||||
return writer;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool UpdatePrimeTracking(bool passedAllTests, bool lastWasComposite)
|
||||
{
|
||||
if (passedAllTests)
|
||||
{
|
||||
int newCount = Interlocked.Increment(ref _primeCount);
|
||||
if (newCount >= 2)
|
||||
{
|
||||
Volatile.Write(ref _primeFoundAfterInit, true);
|
||||
}
|
||||
}
|
||||
|
||||
return passedAllTests && !lastWasComposite && Volatile.Read(ref _primeFoundAfterInit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner;
|
||||
|
||||
internal readonly struct CliArguments
|
||||
{
|
||||
private const string DefaultCyclesPath = "divisor_cycles.bin";
|
||||
|
||||
internal readonly ulong StartPrime;
|
||||
internal readonly bool StartPrimeProvided;
|
||||
internal readonly UInt128 MaxPrimeLimit;
|
||||
internal readonly bool MaxPrimeConfigured;
|
||||
internal readonly int ThreadCount;
|
||||
internal readonly int BlockSize;
|
||||
internal readonly int GpuPrimeThreads;
|
||||
internal readonly int GpuPrimeBatch;
|
||||
internal readonly GpuKernelType KernelType;
|
||||
internal readonly bool UseOrder;
|
||||
internal readonly bool ShowHelp;
|
||||
internal readonly bool UseBitTransform;
|
||||
internal readonly bool UseLucas;
|
||||
internal readonly bool UseResidue;
|
||||
internal readonly bool UseDivisor;
|
||||
internal readonly bool UseByDivisor;
|
||||
internal readonly bool UseGcdFilter;
|
||||
internal readonly bool TestMode;
|
||||
internal readonly bool UseGpuCycles;
|
||||
internal readonly bool UseMersenneOnGpu;
|
||||
internal readonly bool UseOrderOnGpu;
|
||||
internal readonly int ScanBatchSize;
|
||||
internal readonly int SliceSize;
|
||||
internal readonly ulong ResidueKMax;
|
||||
internal readonly string FilterFile;
|
||||
internal readonly bool ForcePrimeKernelsOnCpu;
|
||||
internal readonly ModReductionMode ModReductionMode;
|
||||
internal readonly NttBackend NttBackend;
|
||||
internal readonly string? ResultsDirectory;
|
||||
internal readonly string? ResultsPrefix;
|
||||
internal readonly int WriteBatchSize;
|
||||
internal readonly string CyclesPath;
|
||||
internal readonly bool ContinueCyclesGeneration;
|
||||
internal readonly int CyclesBatchSize;
|
||||
internal readonly ulong DivisorCyclesSearchLimit;
|
||||
internal readonly ulong? OrderWarmupLimitOverride;
|
||||
internal readonly string? RleBlacklistPath;
|
||||
internal readonly ulong RleHardMaxP;
|
||||
internal readonly bool RleOnlyLast7;
|
||||
internal readonly double ZeroFractionHard;
|
||||
internal readonly double ZeroFractionConjecture;
|
||||
internal readonly int MaxZeroConjecture;
|
||||
|
||||
private CliArguments(
|
||||
ulong startPrime,
|
||||
bool startPrimeProvided,
|
||||
UInt128 maxPrimeLimit,
|
||||
bool maxPrimeConfigured,
|
||||
int threadCount,
|
||||
int blockSize,
|
||||
int gpuPrimeThreads,
|
||||
int gpuPrimeBatch,
|
||||
GpuKernelType kernelType,
|
||||
bool useOrder,
|
||||
bool showHelp,
|
||||
bool useBitTransform,
|
||||
bool useLucas,
|
||||
bool useResidue,
|
||||
bool useDivisor,
|
||||
bool useByDivisor,
|
||||
bool useGcdFilter,
|
||||
bool testMode,
|
||||
bool useGpuCycles,
|
||||
bool useMersenneOnGpu,
|
||||
bool useOrderOnGpu,
|
||||
int scanBatchSize,
|
||||
int sliceSize,
|
||||
ulong residueKMax,
|
||||
string filterFile,
|
||||
bool forcePrimeKernelsOnCpu,
|
||||
ModReductionMode modReductionMode,
|
||||
NttBackend nttBackend,
|
||||
string? resultsDirectory,
|
||||
string? resultsPrefix,
|
||||
int writeBatchSize,
|
||||
string cyclesPath,
|
||||
bool continueCyclesGeneration,
|
||||
int cyclesBatchSize,
|
||||
ulong divisorCyclesSearchLimit,
|
||||
ulong? orderWarmupLimitOverride,
|
||||
string? rleBlacklistPath,
|
||||
ulong rleHardMaxP,
|
||||
bool rleOnlyLast7,
|
||||
double zeroFractionHard,
|
||||
double zeroFractionConjecture,
|
||||
int maxZeroConjecture)
|
||||
{
|
||||
StartPrime = startPrime;
|
||||
StartPrimeProvided = startPrimeProvided;
|
||||
MaxPrimeLimit = maxPrimeLimit;
|
||||
MaxPrimeConfigured = maxPrimeConfigured;
|
||||
ThreadCount = threadCount;
|
||||
BlockSize = blockSize;
|
||||
GpuPrimeThreads = gpuPrimeThreads;
|
||||
GpuPrimeBatch = gpuPrimeBatch;
|
||||
KernelType = kernelType;
|
||||
UseOrder = useOrder;
|
||||
ShowHelp = showHelp;
|
||||
UseBitTransform = useBitTransform;
|
||||
UseLucas = useLucas;
|
||||
UseResidue = useResidue;
|
||||
UseDivisor = useDivisor;
|
||||
UseByDivisor = useByDivisor;
|
||||
UseGcdFilter = useGcdFilter;
|
||||
TestMode = testMode;
|
||||
UseGpuCycles = useGpuCycles;
|
||||
UseMersenneOnGpu = useMersenneOnGpu;
|
||||
UseOrderOnGpu = useOrderOnGpu;
|
||||
ScanBatchSize = scanBatchSize;
|
||||
SliceSize = sliceSize;
|
||||
ResidueKMax = residueKMax;
|
||||
FilterFile = filterFile;
|
||||
ForcePrimeKernelsOnCpu = forcePrimeKernelsOnCpu;
|
||||
ModReductionMode = modReductionMode;
|
||||
NttBackend = nttBackend;
|
||||
ResultsDirectory = resultsDirectory;
|
||||
ResultsPrefix = resultsPrefix;
|
||||
WriteBatchSize = writeBatchSize;
|
||||
CyclesPath = cyclesPath;
|
||||
ContinueCyclesGeneration = continueCyclesGeneration;
|
||||
CyclesBatchSize = cyclesBatchSize;
|
||||
DivisorCyclesSearchLimit = divisorCyclesSearchLimit;
|
||||
OrderWarmupLimitOverride = orderWarmupLimitOverride;
|
||||
RleBlacklistPath = rleBlacklistPath;
|
||||
RleHardMaxP = rleHardMaxP;
|
||||
RleOnlyLast7 = rleOnlyLast7;
|
||||
ZeroFractionHard = zeroFractionHard;
|
||||
ZeroFractionConjecture = zeroFractionConjecture;
|
||||
MaxZeroConjecture = maxZeroConjecture;
|
||||
}
|
||||
|
||||
internal bool UseFilter => !TestMode && !string.IsNullOrEmpty(FilterFile);
|
||||
|
||||
internal static CliArguments Parse(string[] args)
|
||||
{
|
||||
ulong startPrime = PerfectNumberConstants.BiggestKnownEvenPerfectP;
|
||||
bool startPrimeProvided = false;
|
||||
UInt128 maxPrimeLimit = UInt128.MaxValue;
|
||||
bool maxPrimeConfigured = false;
|
||||
int threadCount = Environment.ProcessorCount;
|
||||
int blockSize = 1;
|
||||
int gpuPrimeThreads = 1;
|
||||
int gpuPrimeBatch = 262_144;
|
||||
GpuKernelType kernelType = GpuKernelType.Incremental;
|
||||
bool useOrder = false;
|
||||
bool showHelp = false;
|
||||
bool useBitTransform = false;
|
||||
bool useLucas = false;
|
||||
bool useResidue = false;
|
||||
bool useDivisor = false;
|
||||
bool useByDivisor = false;
|
||||
bool useGcdFilter = false;
|
||||
bool testMode = false;
|
||||
bool useGpuCycles = true;
|
||||
bool useMersenneOnGpu = true;
|
||||
bool useOrderOnGpu = true;
|
||||
int scanBatchSize = 2_097_152;
|
||||
int sliceSize = 32;
|
||||
ulong residueKMax = 5_000_000UL;
|
||||
string filterFile = string.Empty;
|
||||
bool forcePrimeKernelsOnCpu = false;
|
||||
ModReductionMode modReductionMode = ModReductionMode.Auto;
|
||||
NttBackend nttBackend = NttBackend.Reference;
|
||||
string? resultsDirectory = null;
|
||||
string? resultsPrefix = null;
|
||||
int writeBatchSize = CalculationResultHandler.DefaultWriteBatchSize;
|
||||
string cyclesPath = DefaultCyclesPath;
|
||||
bool continueCyclesGeneration = false;
|
||||
int cyclesBatchSize = 512;
|
||||
ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
|
||||
ulong? orderWarmupLimitOverride = null;
|
||||
string? rleBlacklistPath = null;
|
||||
ulong rleHardMaxP = ulong.MaxValue;
|
||||
bool rleOnlyLast7 = true;
|
||||
double zeroFractionHard = -1.0;
|
||||
double zeroFractionConjecture = -1.0;
|
||||
int maxZeroConjecture = -1;
|
||||
|
||||
foreach (string argument in args)
|
||||
{
|
||||
if (argument.Equals("--?", StringComparison.OrdinalIgnoreCase) ||
|
||||
argument.Equals("-?", StringComparison.OrdinalIgnoreCase) ||
|
||||
argument.Equals("--help", StringComparison.OrdinalIgnoreCase) ||
|
||||
argument.Equals("-help", StringComparison.OrdinalIgnoreCase) ||
|
||||
argument.Equals("/?", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
showHelp = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
startPrime = Utf8CliParser.ParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1));
|
||||
startPrimeProvided = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
|
||||
{
|
||||
maxPrimeLimit = parsedMaxPrime;
|
||||
maxPrimeConfigured = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Invalid value for --max-prime.");
|
||||
return default;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.Equals("--increment=bit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useBitTransform = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.Equals("--increment=add", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useBitTransform = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--threads=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
if (value.Equals("pow2mod", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
kernelType = GpuKernelType.Pow2Mod;
|
||||
}
|
||||
else if (value.Equals("lucas", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useLucas = true;
|
||||
}
|
||||
else if (value.Equals("residue", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useResidue = true;
|
||||
useLucas = false;
|
||||
}
|
||||
else if (value.Equals("divisor", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useDivisor = true;
|
||||
useLucas = false;
|
||||
useResidue = false;
|
||||
}
|
||||
else if (value.Equals("bydivisor", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useByDivisor = true;
|
||||
useLucas = false;
|
||||
useResidue = false;
|
||||
useDivisor = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
kernelType = GpuKernelType.Incremental;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedLimit))
|
||||
{
|
||||
divisorCyclesSearchLimit = parsedLimit;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedResidueMax))
|
||||
{
|
||||
residueKMax = parsedResidueMax;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useMersenneOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.Equals("--test", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
testMode = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.Equals("--use-order", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useOrder = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--order-warmup-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedLimit))
|
||||
{
|
||||
orderWarmupLimitOverride = parsedLimit;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.Equals("--gcd-filter", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useGcdFilter = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
nttBackend = value.Equals("staged", StringComparison.OrdinalIgnoreCase) ? NttBackend.Staged : NttBackend.Reference;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--mod-reduction=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modReductionMode = ModReductionMode.Mont64;
|
||||
}
|
||||
else if (value.Equals("barrett128", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modReductionMode = ModReductionMode.Barrett128;
|
||||
}
|
||||
else if (value.Equals("uint128", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modReductionMode = ModReductionMode.GpuUInt128;
|
||||
}
|
||||
else
|
||||
{
|
||||
modReductionMode = ModReductionMode.Auto;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--primes-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useOrderOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
rleBlacklistPath = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--rle-hard-max=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedMax))
|
||||
{
|
||||
rleHardMaxP = parsedMax;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--rle-only-last7=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
rleOnlyLast7 = !value.Equals("false", StringComparison.OrdinalIgnoreCase) && !value.Equals("0", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--zero-hard=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseDouble(argument.AsSpan(argument.IndexOf('=') + 1), out double zeroFraction))
|
||||
{
|
||||
zeroFractionHard = zeroFraction;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--zero-conj=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
int colon = value.IndexOf(':');
|
||||
if (colon > 0)
|
||||
{
|
||||
if (Utf8CliParser.TryParseDouble(value[..colon], out double zeroFrac) &&
|
||||
Utf8CliParser.TryParseInt32(value[(colon + 1)..], out int maxZero))
|
||||
{
|
||||
zeroFractionConjecture = zeroFrac;
|
||||
maxZeroConjecture = maxZero;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--lucas=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useMersenneOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--primes=", StringComparison.OrdinalIgnoreCase) ||
|
||||
argument.StartsWith("--gpu-kernels=", StringComparison.OrdinalIgnoreCase) ||
|
||||
argument.StartsWith("--accelerator=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--results-dir=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resultsDirectory = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--results-prefix=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resultsPrefix = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--gpu-prime-threads=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
gpuPrimeThreads = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--gpu-prime-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
gpuPrimeBatch = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--ll-slice=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sliceSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--gpu-scan-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
scanBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--block-size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
blockSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filterFile = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--write-batch-size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cyclesPath = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useGpuCycles = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.Equals("--divisor-cycles-continue", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continueCyclesGeneration = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new CliArguments(
|
||||
startPrime,
|
||||
startPrimeProvided,
|
||||
maxPrimeLimit,
|
||||
maxPrimeConfigured,
|
||||
threadCount,
|
||||
blockSize,
|
||||
gpuPrimeThreads,
|
||||
gpuPrimeBatch,
|
||||
kernelType,
|
||||
useOrder,
|
||||
showHelp,
|
||||
useBitTransform,
|
||||
useLucas,
|
||||
useResidue,
|
||||
useDivisor,
|
||||
useByDivisor,
|
||||
useGcdFilter,
|
||||
testMode,
|
||||
useGpuCycles,
|
||||
useMersenneOnGpu,
|
||||
useOrderOnGpu,
|
||||
scanBatchSize,
|
||||
sliceSize,
|
||||
residueKMax,
|
||||
filterFile,
|
||||
forcePrimeKernelsOnCpu,
|
||||
modReductionMode,
|
||||
nttBackend,
|
||||
resultsDirectory,
|
||||
resultsPrefix,
|
||||
writeBatchSize,
|
||||
cyclesPath,
|
||||
continueCyclesGeneration,
|
||||
cyclesBatchSize,
|
||||
divisorCyclesSearchLimit,
|
||||
orderWarmupLimitOverride,
|
||||
rleBlacklistPath,
|
||||
rleHardMaxP,
|
||||
rleOnlyLast7,
|
||||
zeroFractionHard,
|
||||
zeroFractionConjecture,
|
||||
maxZeroConjecture);
|
||||
}
|
||||
}
|
||||
+163
-554
@@ -1,10 +1,8 @@
|
||||
using System.Buffers;
|
||||
using System.Buffers.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Open.Collections;
|
||||
using Open.Numeric.Primes;
|
||||
using PerfectNumbers.Core;
|
||||
@@ -19,26 +17,18 @@ internal static class Program
|
||||
private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!;
|
||||
private static ThreadLocal<ModResidueTracker> PResidue = null!; // p mod d tracker (per-thread)
|
||||
private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP;
|
||||
private const int WriteBatchSize = 100;
|
||||
private const string DefaultCyclesPath = "divisor_cycles.bin";
|
||||
private static string ResultsFileName = "even_perfect_bit_scan_results.csv";
|
||||
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
||||
private static StringBuilder? _outputBuilder;
|
||||
private static readonly object Sync = new();
|
||||
private static readonly object FileWriteSync = new();
|
||||
private static int _consoleCounter;
|
||||
private static int _writeIndex;
|
||||
private static int _primeCount;
|
||||
private static bool _primeFoundAfterInit;
|
||||
private static bool _useGcdFilter;
|
||||
private static bool _useDivisor;
|
||||
private static bool _useResidueMode;
|
||||
private static bool _useByDivisorMode;
|
||||
private static MersenneNumberDivisorGpuTester? _divisorTester;
|
||||
private static IMersenneNumberDivisorByDivisorTester? _byDivisorTester;
|
||||
private static Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? _byDivisorPreviousResults;
|
||||
private static ulong? _orderWarmupLimitOverride;
|
||||
private static PrimeTransformMode _transformMode;
|
||||
private static long _state;
|
||||
private static bool _limitReached;
|
||||
private static readonly Optimized PrimeIterator = new();
|
||||
private static ulong _byDivisorStartPrime;
|
||||
private static CliArguments _cliArguments;
|
||||
private static CalculationResultHandler _resultHandler;
|
||||
private static int _testTargetPrimeCount;
|
||||
private static int _testProcessedPrimeCount;
|
||||
private const string TestTimeFileName = "even_perfect_test_time.txt";
|
||||
|
||||
private enum PrimeTransformMode
|
||||
{
|
||||
@@ -46,343 +36,74 @@ internal static class Program
|
||||
Add,
|
||||
AddPrimes,
|
||||
}
|
||||
private static long _state;
|
||||
private static bool _limitReached;
|
||||
private static string? _resultsDir;
|
||||
private static string? _resultsPrefix;
|
||||
private static readonly Optimized PrimeIterator = new();
|
||||
private static ulong _byDivisorStartPrime;
|
||||
|
||||
private static bool _testMode;
|
||||
private static int _testTargetPrimeCount;
|
||||
private static int _testProcessedPrimeCount;
|
||||
private const string TestTimeFileName = "even_perfect_test_time.txt";
|
||||
private static PrimeTransformMode TransformMode => _cliArguments.UseBitTransform
|
||||
? PrimeTransformMode.Bit
|
||||
: (_cliArguments.UseResidue
|
||||
? PrimeTransformMode.Add
|
||||
: PrimeTransformMode.AddPrimes);
|
||||
|
||||
[ThreadStatic]
|
||||
private static bool _lastCompositeP;
|
||||
|
||||
// RLE/bit-pattern filtering options
|
||||
private static string? _rleBlacklistPath;
|
||||
private static ulong _rleHardMaxP = ulong.MaxValue;
|
||||
private static bool _rleOnlyLast7 = true;
|
||||
private static int _writeBatchSize = WriteBatchSize;
|
||||
private static double _zeroFracHard = -1.0; // disabled when < 0
|
||||
private static double _zeroFracConj = -1.0; // disabled when < 0
|
||||
private static int _maxZeroConj = -1; // disabled when < 0
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
ulong currentP = InitialP;
|
||||
int threadCount = Environment.ProcessorCount;
|
||||
int blockSize = 1;
|
||||
int gpuPrimeThreads = 1;
|
||||
int gpuPrimeBatch = 262_144;
|
||||
GpuKernelType kernelType = GpuKernelType.Incremental;
|
||||
// removed: useModAutomaton
|
||||
bool useOrder = false;
|
||||
bool showHelp = false;
|
||||
bool useBitTransform = false;
|
||||
bool useLucas = false;
|
||||
bool useResidue = false; // M_p test via residue divisors (replaces LL/incremental)
|
||||
bool useDivisor = false; // M_p divisibility by specific divisor
|
||||
bool useByDivisor = false; // Iterative divisor scan across primes UInt128 divisor = UInt128.Zero;
|
||||
// Device routing
|
||||
bool useGpuCycles = true;
|
||||
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
|
||||
bool orderOnGpu = true; // controls order computations device
|
||||
int scanBatchSize = 2_097_152, sliceSize = 32;
|
||||
ulong residueKMax = 5_000_000UL;
|
||||
string filterFile = string.Empty;
|
||||
UInt128 maxPrimeLimit = UInt128.MaxValue;
|
||||
bool maxPrimeConfigured = false;
|
||||
string cyclesPath = DefaultCyclesPath;
|
||||
int cyclesBatchSize = 512;
|
||||
bool continueCyclesGeneration = false;
|
||||
ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit;
|
||||
int argIndex = 0;
|
||||
string arg = string.Empty;
|
||||
ReadOnlySpan<char> mersenneOption = default;
|
||||
ulong parsedLimit = 0UL;
|
||||
ulong parsedResidueMax = 0UL;
|
||||
ulong remainder = 0UL;
|
||||
bool startPrimeProvided = false;
|
||||
try
|
||||
{
|
||||
CliArguments parsedArguments = CliArguments.Parse(args);
|
||||
if (parsedArguments.ThreadCount == 0 && !parsedArguments.ShowHelp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_testMode = false;
|
||||
_cliArguments = parsedArguments;
|
||||
|
||||
if (_cliArguments.ShowHelp)
|
||||
{
|
||||
PrintHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend;
|
||||
NttGpuMath.ReductionMode = _cliArguments.ModReductionMode;
|
||||
GpuContextPool.ForceCpu = _cliArguments.ForcePrimeKernelsOnCpu;
|
||||
|
||||
ulong currentP = _cliArguments.StartPrime;
|
||||
ulong remainder = currentP % 6UL;
|
||||
bool startPrimeProvided = _cliArguments.StartPrimeProvided;
|
||||
int threadCount = Math.Max(1, _cliArguments.ThreadCount);
|
||||
int blockSize = Math.Max(1, _cliArguments.BlockSize);
|
||||
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
|
||||
int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch);
|
||||
GpuKernelType kernelType = _cliArguments.KernelType;
|
||||
bool useBitTransform = _cliArguments.UseBitTransform;
|
||||
bool useOrder = _cliArguments.UseOrder;
|
||||
bool useResidue = _cliArguments.UseResidue;
|
||||
bool useDivisor = _cliArguments.UseDivisor;
|
||||
bool useByDivisor = _cliArguments.UseByDivisor;
|
||||
bool useLucas = _cliArguments.UseLucas;
|
||||
bool testMode = _cliArguments.TestMode;
|
||||
bool useGpuCycles = _cliArguments.UseGpuCycles;
|
||||
bool mersenneOnGpu = _cliArguments.UseMersenneOnGpu;
|
||||
bool orderOnGpu = _cliArguments.UseOrderOnGpu;
|
||||
int scanBatchSize = Math.Max(1, _cliArguments.ScanBatchSize);
|
||||
int sliceSize = Math.Max(1, _cliArguments.SliceSize);
|
||||
ulong residueKMax = _cliArguments.ResidueKMax;
|
||||
string filterFile = _cliArguments.FilterFile;
|
||||
UInt128 maxPrimeLimit = _cliArguments.MaxPrimeLimit;
|
||||
bool maxPrimeConfigured = _cliArguments.MaxPrimeConfigured;
|
||||
string cyclesPath = _cliArguments.CyclesPath;
|
||||
int cyclesBatchSize = Math.Max(1, _cliArguments.CyclesBatchSize);
|
||||
bool continueCyclesGeneration = _cliArguments.ContinueCyclesGeneration;
|
||||
ulong divisorCyclesSearchLimit = _cliArguments.DivisorCyclesSearchLimit;
|
||||
ulong? orderWarmupLimitOverride = _cliArguments.OrderWarmupLimitOverride;
|
||||
int testPrimeCandidateLimit = 0;
|
||||
|
||||
_limitReached = false;
|
||||
_testTargetPrimeCount = 0;
|
||||
_testProcessedPrimeCount = 0;
|
||||
|
||||
// NTT backend selection (GPU): reference vs staged
|
||||
for (; argIndex < args.Length; argIndex++)
|
||||
{
|
||||
arg = args[argIndex];
|
||||
if (arg.Equals("--?", StringComparison.OrdinalIgnoreCase) || arg.Equals("-?", StringComparison.OrdinalIgnoreCase) || arg.Equals("--help", StringComparison.OrdinalIgnoreCase) || arg.Equals("-help", StringComparison.OrdinalIgnoreCase) || arg.Equals("/?", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
showHelp = true;
|
||||
break;
|
||||
}
|
||||
else if (arg.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
currentP = Utf8CliParser.ParseUInt64(arg.AsSpan(arg.IndexOf('=') + 1));
|
||||
startPrimeProvided = true;
|
||||
}
|
||||
else if (arg.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||
if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
|
||||
{
|
||||
maxPrimeLimit = parsedMaxPrime;
|
||||
maxPrimeConfigured = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Invalid value for --max-prime.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (arg.Equals("--increment=bit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useBitTransform = true;
|
||||
}
|
||||
else if (arg.Equals("--increment=add", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useBitTransform = false;
|
||||
}
|
||||
else if (arg.StartsWith("--threads=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
threadCount = Math.Max(1, Utf8CliParser.ParseInt32(arg.AsSpan(arg.IndexOf('=') + 1)));
|
||||
}
|
||||
else if (arg.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mersenneOption = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||
if (mersenneOption.Equals("pow2mod", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
kernelType = GpuKernelType.Pow2Mod; // Dispatch to the ProcessEightBitWindows pow2mod kernel.
|
||||
}
|
||||
else if (mersenneOption.Equals("lucas", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useLucas = true;
|
||||
}
|
||||
else if (mersenneOption.Equals("residue", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useResidue = true;
|
||||
useLucas = false;
|
||||
}
|
||||
else if (mersenneOption.Equals("divisor", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useDivisor = true;
|
||||
useLucas = false;
|
||||
useResidue = false;
|
||||
}
|
||||
else if (mersenneOption.Equals("bydivisor", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useByDivisor = true;
|
||||
useLucas = false;
|
||||
useResidue = false;
|
||||
useDivisor = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
kernelType = GpuKernelType.Incremental;
|
||||
}
|
||||
}
|
||||
else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(arg.AsSpan(arg.IndexOf('=') + 1), out parsedLimit))
|
||||
{
|
||||
divisorCyclesSearchLimit = parsedLimit;
|
||||
}
|
||||
}
|
||||
else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(arg.AsSpan(arg.IndexOf('=') + 1), out parsedResidueMax))
|
||||
{
|
||||
residueKMax = parsedResidueMax;
|
||||
}
|
||||
}
|
||||
// Replaces --lucas=cpu|gpu; controls device for Lucas and for
|
||||
// incremental/pow2mod scanning
|
||||
else if (arg.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mersenneOnGpu = !arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (arg.Equals("--test", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_testMode = true;
|
||||
}
|
||||
else if (arg.Equals("--use-order", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useOrder = true;
|
||||
}
|
||||
else if (arg.StartsWith("--order-warmup-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// stored and used below when initializing testers
|
||||
// Reusing parsedLimit to capture the --order-warmup-limit argument.
|
||||
if (Utf8CliParser.TryParseUInt64(arg.AsSpan(arg.IndexOf('=') + 1), out parsedLimit))
|
||||
{
|
||||
_orderWarmupLimitOverride = parsedLimit;
|
||||
}
|
||||
}
|
||||
else if (arg.Equals("--gcd-filter", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_useGcdFilter = true;
|
||||
}
|
||||
else if (arg.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||
if (value.Equals("staged", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
}
|
||||
else
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
}
|
||||
}
|
||||
else if (arg.StartsWith("--mod-reduction=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||
if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NttGpuMath.ReductionMode = ModReductionMode.Mont64;
|
||||
}
|
||||
else if (value.Equals("barrett128", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NttGpuMath.ReductionMode = ModReductionMode.Barrett128;
|
||||
}
|
||||
else if (value.Equals("uint128", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NttGpuMath.ReductionMode = ModReductionMode.GpuUInt128;
|
||||
}
|
||||
else
|
||||
{
|
||||
NttGpuMath.ReductionMode = ModReductionMode.Auto;
|
||||
}
|
||||
}
|
||||
else if (arg.StartsWith("--primes-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Controls default device for library prime-related GPU kernels
|
||||
// that are not explicitly parameterized (backward compatibility).
|
||||
GpuContextPool.ForceCpu = arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
// New: choose device for order computations (warm-ups and order scans)
|
||||
else if (arg.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
orderOnGpu = !arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
// Optional: RLE blacklist for p
|
||||
else if (arg.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_rleBlacklistPath = arg[(arg.IndexOf('=') + 1)..];
|
||||
}
|
||||
else if (arg.StartsWith("--rle-hard-max=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ulong rleMaxP;
|
||||
if (Utf8CliParser.TryParseUInt64(arg.AsSpan(arg.IndexOf('=') + 1), out rleMaxP))
|
||||
{
|
||||
_rleHardMaxP = rleMaxP;
|
||||
}
|
||||
}
|
||||
else if (arg.StartsWith("--rle-only-last7=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||
_rleOnlyLast7 = !value.Equals("false", StringComparison.OrdinalIgnoreCase) && !value.Equals("0", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (arg.StartsWith("--zero-hard=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
double zeroFraction;
|
||||
if (Utf8CliParser.TryParseDouble(arg.AsSpan(arg.IndexOf('=') + 1), out zeroFraction))
|
||||
{
|
||||
_zeroFracHard = zeroFraction;
|
||||
}
|
||||
}
|
||||
else if (arg.StartsWith("--zero-conj=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// format: <zeroFrac>:<maxZeroBlock>
|
||||
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||
int colon = value.IndexOf(':');
|
||||
if (colon > 0)
|
||||
{
|
||||
double zeroFrac;
|
||||
int maxZero;
|
||||
if (Utf8CliParser.TryParseDouble(value[..colon], out zeroFrac) && Utf8CliParser.TryParseInt32(value[(colon + 1)..], out maxZero))
|
||||
{
|
||||
_zeroFracConj = zeroFrac;
|
||||
_maxZeroConj = maxZero;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Backward-compat: accept deprecated device flags and map to new ones
|
||||
else if (arg.StartsWith("--lucas=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mersenneOnGpu = !arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (arg.StartsWith("--primes=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
GpuContextPool.ForceCpu = arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (arg.StartsWith("--gpu-kernels=", StringComparison.OrdinalIgnoreCase) || arg.StartsWith("--accelerator=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Legacy alias: treat as primes-device for backward compat
|
||||
GpuContextPool.ForceCpu = arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (arg.StartsWith("--results-dir=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_resultsDir = arg[(arg.IndexOf('=') + 1)..];
|
||||
}
|
||||
else if (arg.StartsWith("--results-prefix=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_resultsPrefix = arg[(arg.IndexOf('=') + 1)..];
|
||||
}
|
||||
|
||||
else if (arg.StartsWith("--gpu-prime-threads=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
gpuPrimeThreads = Math.Max(1, Utf8CliParser.ParseInt32(arg.AsSpan(arg.IndexOf('=') + 1)));
|
||||
}
|
||||
else if (arg.StartsWith("--gpu-prime-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
gpuPrimeBatch = Math.Max(1, Utf8CliParser.ParseInt32(arg.AsSpan(arg.IndexOf('=') + 1)));
|
||||
}
|
||||
else if (arg.StartsWith("--ll-slice=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sliceSize = Math.Max(1, Utf8CliParser.ParseInt32(arg.AsSpan(arg.IndexOf('=') + 1)));
|
||||
}
|
||||
else if (arg.StartsWith("--gpu-scan-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
scanBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(arg.AsSpan(arg.IndexOf('=') + 1)));
|
||||
}
|
||||
else if (arg.StartsWith("--block-size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
blockSize = Math.Max(1, Utf8CliParser.ParseInt32(arg.AsSpan(arg.IndexOf('=') + 1)));
|
||||
}
|
||||
else if (arg.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filterFile = arg[(arg.IndexOf('=') + 1)..];
|
||||
}
|
||||
else if (arg.StartsWith("--write-batch-size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(arg.AsSpan(arg.IndexOf('=') + 1)));
|
||||
}
|
||||
if (arg.StartsWith("--divisor-cycles="))
|
||||
{
|
||||
cyclesPath = arg[(arg.IndexOf('=') + 1)..];
|
||||
}
|
||||
else if (arg.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useGpuCycles = !arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (arg.StartsWith("--divisor-cycles-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(arg.AsSpan(arg.IndexOf('=') + 1)));
|
||||
}
|
||||
else if (arg.Equals("--divisor-cycles-continue", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continueCyclesGeneration = true;
|
||||
}
|
||||
}
|
||||
|
||||
int testPrimeCandidateLimit = 0;
|
||||
|
||||
if (_testMode)
|
||||
if (testMode)
|
||||
{
|
||||
currentP = 31UL;
|
||||
remainder = currentP % 6UL;
|
||||
@@ -402,18 +123,12 @@ internal static class Program
|
||||
_byDivisorStartPrime = 0UL;
|
||||
}
|
||||
|
||||
if (useByDivisor && !_testMode && string.IsNullOrEmpty(filterFile))
|
||||
if (useByDivisor && !testMode && string.IsNullOrEmpty(filterFile))
|
||||
{
|
||||
Console.WriteLine("--mersenne=bydivisor requires --filter-p=<path>.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (showHelp)
|
||||
{
|
||||
PrintHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply GPU prime sieve runtime configuration
|
||||
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
|
||||
PrimeTester.GpuBatchSize = Math.Max(1, gpuPrimeBatch);
|
||||
@@ -477,26 +192,11 @@ internal static class Program
|
||||
blockSize = 1;
|
||||
}
|
||||
|
||||
_useDivisor = useDivisor;
|
||||
_useResidueMode = useResidue;
|
||||
_useByDivisorMode = useByDivisor;
|
||||
if (useBitTransform)
|
||||
{
|
||||
_transformMode = PrimeTransformMode.Bit;
|
||||
}
|
||||
else if (useResidue)
|
||||
{
|
||||
_transformMode = PrimeTransformMode.Add;
|
||||
}
|
||||
else
|
||||
{
|
||||
_transformMode = PrimeTransformMode.AddPrimes;
|
||||
}
|
||||
PResidue = new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, initialNumber: currentP, initialized: true), trackAllValues: true);
|
||||
PrimeTesters = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
|
||||
// Note: --primes-device controls default device for library kernels; p primality remains CPU here.
|
||||
// Initialize per-thread p residue tracker (Identity model) at currentP
|
||||
if (!useDivisor && !useByDivisor)
|
||||
if (!useDivisor && !_cliArguments.UseByDivisor)
|
||||
{
|
||||
MersenneTesters = new ThreadLocal<MersenneNumberTester>(() =>
|
||||
{
|
||||
@@ -514,7 +214,7 @@ internal static class Program
|
||||
if (!useLucas)
|
||||
{
|
||||
Console.WriteLine("Warming up orders");
|
||||
tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
tester.WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
|
||||
return tester;
|
||||
@@ -535,9 +235,9 @@ internal static class Program
|
||||
}
|
||||
|
||||
// Load RLE blacklist (optional)
|
||||
if (!string.IsNullOrEmpty(_rleBlacklistPath))
|
||||
if (!string.IsNullOrEmpty(_cliArguments.RleBlacklistPath))
|
||||
{
|
||||
RleBlacklist.Load(_rleBlacklistPath!);
|
||||
RleBlacklist.Load(_cliArguments.RleBlacklistPath!);
|
||||
}
|
||||
|
||||
// Mod6 lookup turned out slower (see Mod6ComparisonBenchmarks), so keep `%` here for large candidates.
|
||||
@@ -568,23 +268,28 @@ internal static class Program
|
||||
useByDivisor,
|
||||
mersenneOnGpu,
|
||||
useOrder,
|
||||
_useGcdFilter,
|
||||
_cliArguments.UseGcdFilter,
|
||||
NttGpuMath.GpuTransformBackend,
|
||||
gpuPrimeThreads,
|
||||
sliceSize,
|
||||
scanBatchSize,
|
||||
_orderWarmupLimitOverride ?? 5_000_000UL,
|
||||
orderWarmupLimitOverride ?? 5_000_000UL,
|
||||
NttGpuMath.ReductionMode,
|
||||
mersenneOnGpu ? "gpu" : "cpu",
|
||||
(GpuContextPool.ForceCpu ? "cpu" : "gpu"),
|
||||
orderOnGpu ? "gpu" : "cpu");
|
||||
|
||||
if (!string.IsNullOrEmpty(_resultsPrefix))
|
||||
if (!string.IsNullOrEmpty(_cliArguments.ResultsPrefix))
|
||||
{
|
||||
builtName = _resultsPrefix + "_" + builtName;
|
||||
builtName = _cliArguments.ResultsPrefix + "_" + builtName;
|
||||
}
|
||||
ResultsFileName = string.IsNullOrEmpty(_resultsDir) ? builtName : Path.Combine(_resultsDir!, builtName);
|
||||
var dir = Path.GetDirectoryName(ResultsFileName);
|
||||
|
||||
string resultsFileName = string.IsNullOrEmpty(_cliArguments.ResultsDirectory)
|
||||
? builtName
|
||||
: Path.Combine(_cliArguments.ResultsDirectory!, builtName);
|
||||
|
||||
_resultHandler = new CalculationResultHandler(resultsFileName, _cliArguments.WriteBatchSize);
|
||||
var dir = Path.GetDirectoryName(resultsFileName);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir!);
|
||||
@@ -594,15 +299,12 @@ internal static class Program
|
||||
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults = useByDivisor ? new Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>() : null;
|
||||
_byDivisorPreviousResults = previousResults;
|
||||
|
||||
if (File.Exists(ResultsFileName))
|
||||
if (File.Exists(resultsFileName))
|
||||
{
|
||||
Console.WriteLine("Processing previous results...");
|
||||
LoadResultsFile(ResultsFileName, (p, detailedCheck, passedAllTests) =>
|
||||
LoadResultsFile(resultsFileName, (p, detailedCheck, passedAllTests) =>
|
||||
{
|
||||
if (detailedCheck && passedAllTests)
|
||||
{
|
||||
_primeCount++;
|
||||
}
|
||||
_resultHandler.RegisterExistingResult(detailedCheck, passedAllTests);
|
||||
|
||||
if (previousResults is not null)
|
||||
{
|
||||
@@ -612,22 +314,19 @@ internal static class Program
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Replace this File.WriteAllText call with the pooled TextFileWriter pipeline once the
|
||||
// scanner's output flush adopts the benchmarked buffered writer so we avoid allocating the
|
||||
// entire header string and opening the file twice per run.
|
||||
File.WriteAllText(ResultsFileName, $"p,searchedMersenne,detailedCheck,passedAllTests{Environment.NewLine}");
|
||||
_resultHandler.CreateResultsFileWithHeader();
|
||||
}
|
||||
|
||||
bool useFilter = !_testMode && !string.IsNullOrEmpty(filterFile);
|
||||
bool useFilter = !testMode && !string.IsNullOrEmpty(filterFile);
|
||||
HashSet<ulong> filter = [];
|
||||
List<ulong> byDivisorCandidates = [];
|
||||
ulong maxP = 0UL;
|
||||
ulong[] localFilter = Array.Empty<ulong>();
|
||||
int filterCount = 0;
|
||||
|
||||
if (_useByDivisorMode)
|
||||
if (useByDivisor)
|
||||
{
|
||||
if (_testMode)
|
||||
if (testMode)
|
||||
{
|
||||
byDivisorCandidates = BuildTestPrimeCandidates(testPrimeCandidateLimit);
|
||||
}
|
||||
@@ -709,17 +408,11 @@ internal static class Program
|
||||
}
|
||||
|
||||
|
||||
if (_testMode)
|
||||
if (testMode)
|
||||
{
|
||||
_testTargetPrimeCount = testPrimeCandidateLimit;
|
||||
}
|
||||
|
||||
if (_primeCount >= 2)
|
||||
{
|
||||
_primeFoundAfterInit = true;
|
||||
}
|
||||
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_resultHandler.InitializeOutputBuffer();
|
||||
|
||||
// Limit GPU concurrency only for prime checks (LL/NTT & GPU order scans).
|
||||
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
|
||||
@@ -727,14 +420,14 @@ internal static class Program
|
||||
PrimeTester.GpuBatchSize = gpuPrimeBatch;
|
||||
|
||||
Stopwatch? stopwatch = null;
|
||||
if (_testMode)
|
||||
if (testMode)
|
||||
{
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
Console.WriteLine("Starting scan...");
|
||||
|
||||
if (_useByDivisorMode)
|
||||
if (useByDivisor)
|
||||
{
|
||||
MersenneNumberDivisorByDivisorTester.Run(
|
||||
byDivisorCandidates,
|
||||
@@ -745,8 +438,8 @@ internal static class Program
|
||||
static () => _lastCompositeP = false,
|
||||
PrintResult,
|
||||
threadCount);
|
||||
FlushBuffer();
|
||||
StringBuilderPool.Return(_outputBuilder!);
|
||||
_resultHandler.FlushBuffer();
|
||||
_resultHandler.ReleaseOutputBuffer();
|
||||
if (stopwatch is not null)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
@@ -756,18 +449,18 @@ internal static class Program
|
||||
return;
|
||||
}
|
||||
|
||||
if (!useDivisor && !useByDivisor)
|
||||
if (!useDivisor && !_cliArguments.UseByDivisor)
|
||||
{
|
||||
threadCount = Math.Max(1, threadCount);
|
||||
_ = MersenneTesters.Value;
|
||||
if (!useLucas)
|
||||
{
|
||||
Console.WriteLine("Warming up orders...");
|
||||
MersenneTesters.Value!.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
MersenneTesters.Value!.WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
}
|
||||
|
||||
if (_testMode)
|
||||
if (testMode)
|
||||
{
|
||||
testPrimeCandidateLimit = Math.Max(1, threadCount) * 3;
|
||||
_testTargetPrimeCount = testPrimeCandidateLimit;
|
||||
@@ -854,14 +547,19 @@ internal static class Program
|
||||
reportTestTime = true;
|
||||
}
|
||||
|
||||
FlushBuffer();
|
||||
StringBuilderPool.Return(_outputBuilder!);
|
||||
_resultHandler.FlushBuffer();
|
||||
_resultHandler.ReleaseOutputBuffer();
|
||||
|
||||
if (reportTestTime)
|
||||
{
|
||||
ReportTestTime(testElapsed);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_resultHandler.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ulong> BuildTestPrimeCandidates(int targetCount)
|
||||
{
|
||||
@@ -1073,14 +771,51 @@ internal static class Program
|
||||
string red = reduction switch { ModReductionMode.Mont64 => "mont64", ModReductionMode.Barrett128 => "barrett128", ModReductionMode.GpuUInt128 => "uint128", _ => "auto" };
|
||||
string order = useOrder ? "order-on" : "order-off";
|
||||
string gcd = useGcd ? "gcd-on" : "gcd-off";
|
||||
// TODO: Replace this string interpolation with the pooled ValueStringBuilder pipeline from the
|
||||
// results-writer benchmarks so filename generation reuses the zero-allocation formatter once
|
||||
// we consolidate output paths.
|
||||
return $"even_perfect_bit_scan_inc-{inc}_thr-{threads}_blk-{block}_mers-{mers}_mersdev-{mersenneDevice}_ntt-{ntt}_red-{red}_primesdev-{primesDevice}_{order}_orderdev-{orderDevice}_{gcd}_gputh-{gpuPrimeThreads}_llslice-{llSlice}_scanb-{gpuScanBatch}_warm-{warmupLimit}.csv";
|
||||
Span<char> initialBuffer = stackalloc char[256];
|
||||
var builder = new PooledValueStringBuilder(initialBuffer);
|
||||
try
|
||||
{
|
||||
builder.Append("even_perfect_bit_scan_inc-");
|
||||
builder.Append(inc);
|
||||
builder.Append("_thr-");
|
||||
builder.Append(threads);
|
||||
builder.Append("_blk-");
|
||||
builder.Append(block);
|
||||
builder.Append("_mers-");
|
||||
builder.Append(mers);
|
||||
builder.Append("_mersdev-");
|
||||
builder.Append(mersenneDevice);
|
||||
builder.Append("_ntt-");
|
||||
builder.Append(ntt);
|
||||
builder.Append("_red-");
|
||||
builder.Append(red);
|
||||
builder.Append("_primesdev-");
|
||||
builder.Append(primesDevice);
|
||||
builder.Append('_');
|
||||
builder.Append(order);
|
||||
builder.Append("_orderdev-");
|
||||
builder.Append(orderDevice);
|
||||
builder.Append('_');
|
||||
builder.Append(gcd);
|
||||
builder.Append("_gputh-");
|
||||
builder.Append(gpuPrimeThreads);
|
||||
builder.Append("_llslice-");
|
||||
builder.Append(llSlice);
|
||||
builder.Append("_scanb-");
|
||||
builder.Append(gpuScanBatch);
|
||||
builder.Append("_warm-");
|
||||
builder.Append(warmupLimit);
|
||||
builder.Append(".csv");
|
||||
return builder.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
builder.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong AdvancePrime(ulong value, ref ulong remainder) => _transformMode switch
|
||||
private static ulong AdvancePrime(ulong value, ref ulong remainder) => TransformMode switch
|
||||
{
|
||||
PrimeTransformMode.Bit => TransformPBit(value, ref remainder),
|
||||
PrimeTransformMode.Add => TransformPAdd(value, ref remainder),
|
||||
@@ -1101,7 +836,7 @@ internal static class Program
|
||||
remainder = ((ulong)state) & 7UL;
|
||||
|
||||
count = 0;
|
||||
switch (_transformMode)
|
||||
switch (TransformMode)
|
||||
{
|
||||
case PrimeTransformMode.Bit:
|
||||
while (count < blockSize && !Volatile.Read(ref _limitReached))
|
||||
@@ -1148,7 +883,7 @@ internal static class Program
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void OnPrimeCandidateConfirmed()
|
||||
{
|
||||
if (!_testMode)
|
||||
if (!_cliArguments.TestMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1160,140 +895,14 @@ internal static class Program
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryAcquireConsoleSlot(bool primeCandidate, out bool primeFlag)
|
||||
{
|
||||
int consoleInterval = PerfectNumberConstants.ConsoleInterval;
|
||||
int counterValue = Interlocked.Increment(ref _consoleCounter);
|
||||
if (counterValue >= consoleInterval)
|
||||
{
|
||||
int previous = Interlocked.Exchange(ref _consoleCounter, 0);
|
||||
if (previous >= consoleInterval)
|
||||
{
|
||||
primeFlag = primeCandidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
primeFlag = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static StringBuilder? AppendToOutputBuffer(StringBuilder source)
|
||||
{
|
||||
StringBuilder? builderToFlush = null;
|
||||
|
||||
lock (Sync)
|
||||
{
|
||||
StringBuilder outputBuilder = _outputBuilder!;
|
||||
_ = outputBuilder.Append(source);
|
||||
|
||||
int nextIndex = _writeIndex + 1;
|
||||
if (nextIndex >= _writeBatchSize)
|
||||
{
|
||||
builderToFlush = outputBuilder;
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_writeIndex = nextIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return builderToFlush;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void PrintResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||
{
|
||||
if (passedAllTests)
|
||||
{
|
||||
int newCount = Interlocked.Increment(ref _primeCount);
|
||||
if (newCount >= 2)
|
||||
{
|
||||
Volatile.Write(ref _primeFoundAfterInit, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool lastWasComposite = _lastCompositeP;
|
||||
bool primeCandidate = passedAllTests && !lastWasComposite && Volatile.Read(ref _primeFoundAfterInit);
|
||||
|
||||
// TODO: Switch this StringBuilder-based formatter to a span-friendly Utf8Formatter pipeline so
|
||||
// console/file output avoids intermediate builders in the hot logging loop.
|
||||
StringBuilder localBuilder = StringBuilderPool.Rent();
|
||||
_ = localBuilder
|
||||
.Append(currentP).Append(',')
|
||||
.Append(searchedMersenne).Append(',')
|
||||
.Append(detailedCheck).Append(',')
|
||||
.Append(passedAllTests).Append('\n');
|
||||
|
||||
bool printToConsole = TryAcquireConsoleSlot(primeCandidate, out bool primeFlag);
|
||||
StringBuilder? builderToFlush = AppendToOutputBuffer(localBuilder);
|
||||
|
||||
if (builderToFlush is not null)
|
||||
{
|
||||
FlushBuffer(builderToFlush);
|
||||
}
|
||||
|
||||
if (printToConsole)
|
||||
{
|
||||
if (primeFlag)
|
||||
{
|
||||
_ = localBuilder
|
||||
.Remove(localBuilder.Length - 1, 1)
|
||||
.Append(PrimeFoundSuffix)
|
||||
.Append('\n');
|
||||
}
|
||||
|
||||
Console.WriteLine(localBuilder.Remove(localBuilder.Length - 1, 1).ToString());
|
||||
}
|
||||
|
||||
localBuilder.Clear();
|
||||
StringBuilderPool.Return(localBuilder);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void FlushBuffer()
|
||||
{
|
||||
StringBuilder? builderToFlush = null;
|
||||
|
||||
lock (Sync)
|
||||
{
|
||||
if (_writeIndex == 0 || _outputBuilder is null || _outputBuilder.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builderToFlush = _outputBuilder;
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
|
||||
FlushBuffer(builderToFlush!);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void FlushBuffer(StringBuilder builderToFlush)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (FileWriteSync)
|
||||
{
|
||||
// TODO: Replace these per-flush FileStream/StreamWriter allocations with a pooled TextFileWriter-style
|
||||
// helper so batched result writes keep the hot path on the benchmarked persistent-handle pipeline.
|
||||
using FileStream stream = new(ResultsFileName, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
using StreamWriter writer = new(stream) { AutoFlush = false };
|
||||
writer.Write(builderToFlush);
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
builderToFlush.Clear();
|
||||
StringBuilderPool.Return(builderToFlush);
|
||||
}
|
||||
_resultHandler.HandleResult(currentP, searchedMersenne, detailedCheck, passedAllTests, lastWasComposite);
|
||||
}
|
||||
|
||||
private static void ReportTestTime(TimeSpan elapsed)
|
||||
@@ -1327,12 +936,12 @@ internal static class Program
|
||||
|
||||
private static string GetTestTimeFilePath()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_resultsDir))
|
||||
if (string.IsNullOrEmpty(_cliArguments.ResultsDirectory))
|
||||
{
|
||||
return TestTimeFileName;
|
||||
}
|
||||
|
||||
return Path.Combine(_resultsDir!, TestTimeFileName);
|
||||
return Path.Combine(_cliArguments.ResultsDirectory!, TestTimeFileName);
|
||||
}
|
||||
|
||||
private static bool TryReadTestTime(string filePath, out double milliseconds)
|
||||
@@ -1527,16 +1136,16 @@ internal static class Program
|
||||
detailedCheck = false;
|
||||
_lastCompositeP = false;
|
||||
|
||||
if (_useGcdFilter && IsCompositeByGcd(p))
|
||||
if (_cliArguments.UseGcdFilter && IsCompositeByGcd(p))
|
||||
{
|
||||
_lastCompositeP = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional: RLE blacklist and binary-threshold filters on p (safe only when configured)
|
||||
if (p <= _rleHardMaxP)
|
||||
if (p <= _cliArguments.RleHardMaxP)
|
||||
{
|
||||
if (!_rleOnlyLast7 || p.Mod10() == 7UL)
|
||||
if (!_cliArguments.RleOnlyLast7 || p.Mod10() == 7UL)
|
||||
{
|
||||
if (RleBlacklist.IsLoaded() && RleBlacklist.Matches(p))
|
||||
{
|
||||
@@ -1545,7 +1154,7 @@ internal static class Program
|
||||
}
|
||||
}
|
||||
|
||||
if (_zeroFracHard >= 0 || (_zeroFracConj >= 0 && _maxZeroConj >= 0))
|
||||
if (_cliArguments.ZeroFractionHard >= 0 || (_cliArguments.ZeroFractionConjecture >= 0 && _cliArguments.MaxZeroConjecture >= 0))
|
||||
{
|
||||
double zf;
|
||||
int bitLength;
|
||||
@@ -1553,17 +1162,17 @@ internal static class Program
|
||||
int maxZeroBlockValue;
|
||||
ComputeBitStats(p, out bitLength, out zeroCountValue, out maxZeroBlockValue);
|
||||
zf = (double)zeroCountValue / bitLength;
|
||||
if (_zeroFracHard >= 0)
|
||||
if (_cliArguments.ZeroFractionHard >= 0)
|
||||
{
|
||||
if (zf > _zeroFracHard)
|
||||
if (zf > _cliArguments.ZeroFractionHard)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_zeroFracConj >= 0 && _maxZeroConj >= 0)
|
||||
if (_cliArguments.ZeroFractionConjecture >= 0 && _cliArguments.MaxZeroConjecture >= 0)
|
||||
{
|
||||
if (zf > _zeroFracConj && maxZeroBlockValue >= _maxZeroConj)
|
||||
if (zf > _cliArguments.ZeroFractionConjecture && maxZeroBlockValue >= _cliArguments.MaxZeroConjecture)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1571,7 +1180,7 @@ internal static class Program
|
||||
}
|
||||
|
||||
// Fast residue-based composite check for p using small primes
|
||||
if (!_useDivisor && !_useByDivisorMode && IsCompositeByResidues(p))
|
||||
if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor && IsCompositeByResidues(p))
|
||||
{
|
||||
searchedMersenne = false;
|
||||
detailedCheck = false;
|
||||
@@ -1580,7 +1189,7 @@ internal static class Program
|
||||
}
|
||||
|
||||
// If primes-device=gpu, route p primality through GPU-assisted sieve with deterministic MR validation.
|
||||
if (!_useByDivisorMode)
|
||||
if (!_cliArguments.UseByDivisor)
|
||||
{
|
||||
if (!(GpuContextPool.ForceCpu
|
||||
? PrimeTesters.Value!.IsPrime(p, CancellationToken.None)
|
||||
@@ -1594,7 +1203,7 @@ internal static class Program
|
||||
}
|
||||
|
||||
searchedMersenne = true;
|
||||
if (_useByDivisorMode)
|
||||
if (_cliArguments.UseByDivisor)
|
||||
{
|
||||
// Windowed pow2mod kernel handles by-divisor scans.
|
||||
return _byDivisorTester!.IsPrime(p, out detailedCheck);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
public ref struct PooledValueStringBuilder
|
||||
{
|
||||
private Span<char> _span;
|
||||
private char[]? _arrayFromPool;
|
||||
private int _pos;
|
||||
|
||||
public PooledValueStringBuilder(Span<char> initialBuffer)
|
||||
{
|
||||
_span = initialBuffer;
|
||||
_arrayFromPool = null;
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Append(char value)
|
||||
{
|
||||
int position = _pos;
|
||||
if (position < _span.Length)
|
||||
{
|
||||
_span[position] = value;
|
||||
_pos = position + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
Grow(1);
|
||||
_span[_pos++] = value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Append(string? value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
Append(value.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
public void Append(ReadOnlySpan<char> value)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int remaining = _span.Length - _pos;
|
||||
if (value.Length <= remaining)
|
||||
{
|
||||
value.CopyTo(_span[_pos..]);
|
||||
_pos += value.Length;
|
||||
return;
|
||||
}
|
||||
|
||||
Grow(value.Length);
|
||||
value.CopyTo(_span[_pos..]);
|
||||
_pos += value.Length;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Append(int value)
|
||||
{
|
||||
AppendSpanFormattable(value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Append(ulong value)
|
||||
{
|
||||
AppendSpanFormattable(value);
|
||||
}
|
||||
|
||||
public ReadOnlySpan<char> AsSpan() => _span[.._pos];
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return new string(_span[.._pos]);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_arrayFromPool is { } array)
|
||||
{
|
||||
ThreadStaticPools.CharPool.Return(array);
|
||||
_arrayFromPool = null;
|
||||
}
|
||||
|
||||
_span = Span<char>.Empty;
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
private void AppendSpanFormattable<T>(T value) where T : ISpanFormattable
|
||||
{
|
||||
int charsWritten;
|
||||
while (!value.TryFormat(_span[_pos..], out charsWritten, default, CultureInfo.InvariantCulture))
|
||||
{
|
||||
Grow(Math.Max(4, _span.Length));
|
||||
}
|
||||
|
||||
_pos += charsWritten;
|
||||
}
|
||||
|
||||
private void Grow(int additionalCapacity)
|
||||
{
|
||||
int required = _pos + additionalCapacity;
|
||||
int currentLength = _span.Length;
|
||||
int newCapacity = currentLength == 0 ? Math.Max(4, required) : Math.Max(currentLength * 2, required);
|
||||
char[] newArray = ThreadStaticPools.CharPool.Rent(newCapacity);
|
||||
_span[.._pos].CopyTo(newArray.AsSpan());
|
||||
if (_arrayFromPool is { } toReturn)
|
||||
{
|
||||
ThreadStaticPools.CharPool.Return(toReturn);
|
||||
}
|
||||
|
||||
_span = newArray;
|
||||
_arrayFromPool = newArray;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
/// <summary>
|
||||
@@ -70,6 +73,41 @@ public sealed class TextFileWriter : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteLine(ReadOnlySpan<char> line)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
StreamWriter writer = TextWriter;
|
||||
writer.Write(line);
|
||||
writer.WriteLine();
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<char> text)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
StreamWriter writer = TextWriter;
|
||||
writer.Write(text);
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(StringBuilder builder)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
StreamWriter writer = TextWriter;
|
||||
foreach (ReadOnlyMemory<char> chunk in builder.GetChunks())
|
||||
{
|
||||
writer.Write(chunk.Span);
|
||||
}
|
||||
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a binary writing action within the internal lock.
|
||||
/// </summary>
|
||||
|
||||
@@ -101,6 +101,18 @@ namespace PerfectNumbers.Core
|
||||
}
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static ArrayPool<char>? _charPool;
|
||||
|
||||
public static ArrayPool<char> CharPool
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _charPool ??= ArrayPool<char>.Create();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ThreadStatic]
|
||||
private static List<List<ulong>>? _ulongListPool;
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
Results & Output Pipelines
|
||||
- EvenPerfectBitScanner/Program.cs:615: Replace File.WriteAllText with the pooled TextFileWriter pipeline for the results header.
|
||||
- EvenPerfectBitScanner/Program.cs:1218: Swap the results filename interpolation for the pooled ValueStringBuilder pipeline.
|
||||
- EvenPerfectBitScanner/Program.cs:1335: Move PrintResult to a Utf8Formatter-based pipeline instead of renting StringBuilders per line.
|
||||
- EvenPerfectBitScanner/Program.cs:1396: Replace per-flush FileStream/StreamWriter allocations with a pooled TextFileWriter-style helper.
|
||||
|
||||
Arithmetic Helpers & Bit Statistics
|
||||
- EvenPerfectBitScanner/Program.cs:1483: Remove the CountOnes wrapper once callers invoke BitOperations.PopCount directly.
|
||||
- EvenPerfectBitScanner/Program.cs:1785: Replace the byte-by-byte zero statistics scan with the lookup-table collector validated in the BitStats benchmarks.
|
||||
|
||||
Reference in new issue
Block a user