Merge pull request #200 from mlemanczyk/codex/implement-todos-in-evenperfectbitscanner
Move prime tracking into CalculationResultHandler
This commit is contained in:
7 files changed
+1656
-942
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);
|
||||
}
|
||||
}
|
||||
+545
-936
File diff suppressed because it is too large.
Load diff
@@ -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