Move prime tracking into CalculationResultHandler
This commit is contained in:
1 parent
194f10d00b
commit
e338497c2f
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;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// <summary>
|
||||||
/// Executes a binary writing action within the internal lock.
|
/// Executes a binary writing action within the internal lock.
|
||||||
/// </summary>
|
/// </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]
|
[ThreadStatic]
|
||||||
private static List<List<ulong>>? _ulongListPool;
|
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
|
Arithmetic Helpers & Bit Statistics
|
||||||
- EvenPerfectBitScanner/Program.cs:1483: Remove the CountOnes wrapper once callers invoke BitOperations.PopCount directly.
|
- 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.
|
- 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