Restore initialization guidance comments
This commit is contained in:
1 parent
98afb3ff9e
commit
8108d973ca
5 files changed
+539
-586
No files matched your search
@@ -63,8 +63,8 @@ public class ProgramTests
|
||||
[Fact]
|
||||
public void Gcd_filter_detects_some_composites()
|
||||
{
|
||||
Program.IsCompositeByGcd(15UL).Should().BeTrue();
|
||||
Program.IsCompositeByGcd(5UL).Should().BeFalse();
|
||||
15UL.IsCompositeByGcd().Should().BeTrue();
|
||||
5UL.IsCompositeByGcd().Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,376 +1,340 @@
|
||||
using System.Buffers.Text;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner;
|
||||
|
||||
// TODO: Convert this struct to an equivalent of a static class. Make all fields / properties / methods static and initialize it during program startup. Don't check if it's initialized within. Rely on the flow enforcing it.
|
||||
internal struct CalculationResultHandler
|
||||
internal static class CalculationResultHandler
|
||||
{
|
||||
private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
||||
internal const int DefaultWriteBatchSize = 100;
|
||||
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 static readonly UTF8Encoding Utf8Encoding = new(false, true);
|
||||
private static readonly object Sync = new();
|
||||
private static readonly object FileWriteSync = new();
|
||||
|
||||
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 static StringBuilder? _outputBuilder;
|
||||
private static TextFileWriter? _resultsWriter;
|
||||
private static int _consoleCounter;
|
||||
private static int _writeIndex;
|
||||
private static int _writeBatchSize;
|
||||
private static string _resultsFileName = string.Empty;
|
||||
private static int _primeCount;
|
||||
private static bool _primeFoundAfterInit;
|
||||
|
||||
private readonly bool IsInitialized => _sync is not null;
|
||||
private readonly bool HasFileSync => _fileWriteSync is not null;
|
||||
internal static string ResultsFileName => _resultsFileName;
|
||||
|
||||
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 static void Initialize(string resultsFileName, int writeBatchSize)
|
||||
{
|
||||
Dispose();
|
||||
|
||||
internal readonly string ResultsFileName => _resultsFileName;
|
||||
_outputBuilder = null;
|
||||
_resultsWriter = null;
|
||||
_consoleCounter = 0;
|
||||
_writeIndex = 0;
|
||||
_writeBatchSize = writeBatchSize > 0 ? writeBatchSize : DefaultWriteBatchSize;
|
||||
_resultsFileName = resultsFileName;
|
||||
_primeCount = 0;
|
||||
_primeFoundAfterInit = false;
|
||||
}
|
||||
|
||||
[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;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void InitializeOutputBuffer()
|
||||
{
|
||||
// Program flow ensures Initialize(...) runs before this call. Do not add null guards here.
|
||||
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
_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;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void ReleaseOutputBuffer()
|
||||
{
|
||||
// Program flow ensures InitializeOutputBuffer() populated the builder. Do not add null checks here.
|
||||
|
||||
StringBuilder builder = _outputBuilder!;
|
||||
// Program flow guarantees the output builder is populated when releasing. Do not uncomment the guard below.
|
||||
// if (builder is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
StringBuilder builder = _outputBuilder!;
|
||||
// Program flow guarantees the output builder is populated when releasing. Do not add guard checks here.
|
||||
|
||||
_outputBuilder = null;
|
||||
_writeIndex = 0;
|
||||
StringBuilderPool.Return(builder);
|
||||
}
|
||||
_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;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void CreateResultsFileWithHeader()
|
||||
{
|
||||
// Program flow guarantees FileWriteSync is available here. Do not add guard checks for the lock object.
|
||||
|
||||
lock (_fileWriteSync)
|
||||
{
|
||||
_resultsWriter?.Dispose();
|
||||
var writer = new TextFileWriter(_resultsFileName, FileMode.Create);
|
||||
writer.WriteLine(ResultsHeader.AsSpan());
|
||||
_resultsWriter = writer;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void RegisterExistingResult(bool detailedCheck, bool passedAllTests)
|
||||
{
|
||||
if (!detailedCheck || !passedAllTests)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int newCount = _primeCount + 1;
|
||||
_primeCount = newCount;
|
||||
if (newCount >= 2)
|
||||
{
|
||||
_primeFoundAfterInit = true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
||||
{
|
||||
// Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
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];
|
||||
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);
|
||||
bool primeCandidate = UpdatePrimeTracking(passedAllTests, lastWasComposite);
|
||||
bool printToConsole = TryAcquireConsoleSlot(primeCandidate, out bool primeFlag);
|
||||
StringBuilder? builderToFlush = AppendToOutputBuffer(recordSpan);
|
||||
|
||||
if (builderToFlush is not null)
|
||||
{
|
||||
FlushBuffer(builderToFlush);
|
||||
}
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
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;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void FlushBuffer()
|
||||
{
|
||||
// Program flow ensures InitializeOutputBuffer() prepared the buffer. Do not add null checks here.
|
||||
|
||||
StringBuilder? builderToFlush;
|
||||
StringBuilder? builderToFlush;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_writeIndex == 0 || _outputBuilder is null || _outputBuilder.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lock (Sync)
|
||||
{
|
||||
if (_writeIndex == 0 || _outputBuilder is null || _outputBuilder.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builderToFlush = _outputBuilder;
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
builderToFlush = _outputBuilder;
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
|
||||
FlushBuffer(builderToFlush!);
|
||||
}
|
||||
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;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Dispose()
|
||||
{
|
||||
// Program flow guarantees FileWriteSync is ready when disposing. Do not add guard checks for the lock object.
|
||||
|
||||
lock (_fileWriteSync)
|
||||
{
|
||||
_resultsWriter?.Dispose();
|
||||
_resultsWriter = null;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
[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;
|
||||
}
|
||||
primeFlag = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private StringBuilder? AppendToOutputBuffer(ReadOnlySpan<char> text)
|
||||
{
|
||||
StringBuilder? builderToFlush = null;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static StringBuilder? AppendToOutputBuffer(ReadOnlySpan<char> text)
|
||||
{
|
||||
StringBuilder? builderToFlush = null;
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
StringBuilder outputBuilder = _outputBuilder!;
|
||||
_ = outputBuilder.Append(text);
|
||||
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;
|
||||
}
|
||||
}
|
||||
int nextIndex = _writeIndex + 1;
|
||||
if (nextIndex >= _writeBatchSize)
|
||||
{
|
||||
builderToFlush = outputBuilder;
|
||||
_outputBuilder = StringBuilderPool.Rent();
|
||||
_writeIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_writeIndex = nextIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return builderToFlush;
|
||||
}
|
||||
return builderToFlush;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||
{
|
||||
int offset = 0;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static 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;
|
||||
}
|
||||
if (!Utf8Formatter.TryFormat(currentP, destination[offset..], out int written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)',';
|
||||
destination[offset++] = (byte)',';
|
||||
|
||||
if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)',';
|
||||
destination[offset++] = (byte)',';
|
||||
|
||||
if (!Utf8Formatter.TryFormat(detailedCheck, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (!Utf8Formatter.TryFormat(detailedCheck, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)',';
|
||||
destination[offset++] = (byte)',';
|
||||
|
||||
if (!Utf8Formatter.TryFormat(passedAllTests, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (!Utf8Formatter.TryFormat(passedAllTests, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)'\n';
|
||||
return offset;
|
||||
}
|
||||
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;
|
||||
// }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void FlushBuffer(StringBuilder builderToFlush)
|
||||
{
|
||||
// Program flow guarantees FileWriteSync is ready here as well. Do not add guard checks for the lock object.
|
||||
|
||||
try
|
||||
{
|
||||
lock (_fileWriteSync)
|
||||
{
|
||||
TextFileWriter writer = EnsureResultsWriter();
|
||||
writer.Write(builderToFlush);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
builderToFlush.Clear();
|
||||
StringBuilderPool.Return(builderToFlush);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static TextFileWriter EnsureResultsWriter()
|
||||
{
|
||||
TextFileWriter? writer = _resultsWriter;
|
||||
if (writer is not null)
|
||||
{
|
||||
return writer;
|
||||
}
|
||||
|
||||
writer = new TextFileWriter(_resultsFileName, FileMode.Append);
|
||||
_resultsWriter = writer;
|
||||
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);
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static 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);
|
||||
}
|
||||
return passedAllTests && !lastWasComposite && Volatile.Read(ref _primeFoundAfterInit);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
@@ -560,4 +561,45 @@ internal readonly struct CliArguments
|
||||
zeroFractionConjecture,
|
||||
maxZeroConjecture);
|
||||
}
|
||||
|
||||
internal static void PrintHelp()
|
||||
{
|
||||
Console.WriteLine("Usage: EvenPerfectBitScanner [options]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Options:");
|
||||
Console.WriteLine(" --prime=<value> starting exponent (p)");
|
||||
Console.WriteLine(" --max-prime=<value> inclusive upper bound for primes from filter files");
|
||||
Console.WriteLine(" --increment=bit|add exponent increment method");
|
||||
Console.WriteLine(" --threads=<value> number of worker threads");
|
||||
Console.WriteLine(" --block-size=<value> values processed per thread batch");
|
||||
Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method");
|
||||
Console.WriteLine(" --residue-max-k=<value> max k for residue Mersenne test (q = 2*p*k + 1)");
|
||||
Console.WriteLine(" --mersenne-device=cpu|gpu Device for Mersenne method (default gpu)");
|
||||
Console.WriteLine(" --primes-device=cpu|gpu Device for prime-scan kernels (default gpu)");
|
||||
Console.WriteLine(" --gpu-prime-batch=<n> Batch size for GPU primality sieve (default 262144)");
|
||||
Console.WriteLine(" --order-device=cpu|gpu Device for order computations (default gpu)");
|
||||
Console.WriteLine(" --ntt=reference|staged GPU NTT backend (default staged)");
|
||||
Console.WriteLine(" --mod-reduction=auto|uint128|mont64|barrett128 staged NTT reduction (default auto)");
|
||||
Console.WriteLine(" --gpu-prime-threads=<value> max concurrent GPU prime checks (default 1)");
|
||||
Console.WriteLine(" --ll-slice=<value> Lucas–Lehmer iterations per slice (default 32)");
|
||||
Console.WriteLine(" --gpu-scan-batch=<value> GPU q-scan batch size (default 2_097_152)");
|
||||
Console.WriteLine(" --order-warmup-limit=<value> Warm-up order candidates (default 5_000_000)");
|
||||
Console.WriteLine(" --rle-blacklist=<path> enable RLE blacklist for p (hard filter up to --rle-hard-max)");
|
||||
Console.WriteLine(" --rle-hard-max=<p> apply RLE blacklist only for p <= this (default ulong.MaxValue = no limit)");
|
||||
Console.WriteLine(" --rle-only-last7=true|false apply RLE only when p % 10 == 7 (default true)");
|
||||
Console.WriteLine(" --zero-hard=<f> hard reject if zero_fraction(p) > f (default off)");
|
||||
Console.WriteLine(" --zero-conj=<f>:<r> hard reject if zero_fraction(p) > f AND max_zero_block >= r (default off)");
|
||||
Console.WriteLine(" --results-dir=<path> directory for results file");
|
||||
Console.WriteLine(" --results-prefix=<text> prefix to prepend to results filename");
|
||||
Console.WriteLine(" --divisor-cycles=<path> divisor cycles data file");
|
||||
Console.WriteLine(" --divisor-cycles-device=cpu|gpu device for cycles generation (default gpu)");
|
||||
Console.WriteLine(" --divisor-cycles-batch=<value> batch size for cycles generation (default 512)");
|
||||
Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation");
|
||||
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor");
|
||||
Console.WriteLine(" --use-order test primality via q order");
|
||||
Console.WriteLine(" --filter-p=<path> process only p from previous run results (required for --mersenne=bydivisor)");
|
||||
Console.WriteLine(" --write-batch-size=<value> overwrite frequency of disk writes (default 100 lines)");
|
||||
Console.WriteLine(" --gcd-filter enable early sieve based on GCD");
|
||||
Console.WriteLine(" --help, -help, --?, -?, /? show this help message");
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ internal static class Program
|
||||
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";
|
||||
@@ -60,7 +59,7 @@ internal static class Program
|
||||
|
||||
if (_cliArguments.ShowHelp)
|
||||
{
|
||||
PrintHelp();
|
||||
CliArguments.PrintHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -288,7 +287,7 @@ internal static class Program
|
||||
? builtName
|
||||
: Path.Combine(_cliArguments.ResultsDirectory!, builtName);
|
||||
|
||||
_resultHandler = new CalculationResultHandler(resultsFileName, _cliArguments.WriteBatchSize);
|
||||
CalculationResultHandler.Initialize(resultsFileName, _cliArguments.WriteBatchSize);
|
||||
var dir = Path.GetDirectoryName(resultsFileName);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
{
|
||||
@@ -304,7 +303,7 @@ internal static class Program
|
||||
Console.WriteLine("Processing previous results...");
|
||||
LoadResultsFile(resultsFileName, (p, detailedCheck, passedAllTests) =>
|
||||
{
|
||||
_resultHandler.RegisterExistingResult(detailedCheck, passedAllTests);
|
||||
CalculationResultHandler.RegisterExistingResult(detailedCheck, passedAllTests);
|
||||
|
||||
if (previousResults is not null)
|
||||
{
|
||||
@@ -314,7 +313,7 @@ internal static class Program
|
||||
}
|
||||
else
|
||||
{
|
||||
_resultHandler.CreateResultsFileWithHeader();
|
||||
CalculationResultHandler.CreateResultsFileWithHeader();
|
||||
}
|
||||
|
||||
bool useFilter = !testMode && !string.IsNullOrEmpty(filterFile);
|
||||
@@ -412,13 +411,12 @@ internal static class Program
|
||||
{
|
||||
_testTargetPrimeCount = testPrimeCandidateLimit;
|
||||
}
|
||||
_resultHandler.InitializeOutputBuffer();
|
||||
CalculationResultHandler.InitializeOutputBuffer();
|
||||
|
||||
// Limit GPU concurrency only for prime checks (LL/NTT & GPU order scans).
|
||||
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
|
||||
// Configure batch size for GPU primality sieve
|
||||
PrimeTester.GpuBatchSize = gpuPrimeBatch;
|
||||
CalculationResultHandler resultHandler = _resultHandler;
|
||||
|
||||
Stopwatch? stopwatch = null;
|
||||
if (testMode)
|
||||
@@ -437,10 +435,10 @@ internal static class Program
|
||||
_byDivisorStartPrime,
|
||||
static () => _lastCompositeP = true,
|
||||
static () => _lastCompositeP = false,
|
||||
static (candidate, searchedMersenne, detailedCheck, passedAllTests) => _resultHandler.HandleResult(candidate, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP),
|
||||
static (candidate, searchedMersenne, detailedCheck, passedAllTests) => CalculationResultHandler.HandleResult(candidate, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP),
|
||||
threadCount);
|
||||
_resultHandler.FlushBuffer();
|
||||
_resultHandler.ReleaseOutputBuffer();
|
||||
CalculationResultHandler.FlushBuffer();
|
||||
CalculationResultHandler.ReleaseOutputBuffer();
|
||||
if (stopwatch is not null)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
@@ -495,7 +493,7 @@ internal static class Program
|
||||
{
|
||||
p = buffer[j];
|
||||
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
|
||||
resultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||
CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -518,7 +516,7 @@ internal static class Program
|
||||
}
|
||||
|
||||
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
|
||||
resultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||
CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||
|
||||
if (p == maxP)
|
||||
{
|
||||
@@ -548,8 +546,8 @@ internal static class Program
|
||||
reportTestTime = true;
|
||||
}
|
||||
|
||||
_resultHandler.FlushBuffer();
|
||||
_resultHandler.ReleaseOutputBuffer();
|
||||
CalculationResultHandler.FlushBuffer();
|
||||
CalculationResultHandler.ReleaseOutputBuffer();
|
||||
|
||||
if (reportTestTime)
|
||||
{
|
||||
@@ -558,7 +556,7 @@ internal static class Program
|
||||
}
|
||||
finally
|
||||
{
|
||||
_resultHandler.Dispose();
|
||||
CalculationResultHandler.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,49 +714,7 @@ internal static class Program
|
||||
}
|
||||
|
||||
|
||||
// TODO: Move this to CliArguments
|
||||
private static void PrintHelp()
|
||||
{
|
||||
Console.WriteLine("Usage: EvenPerfectBitScanner [options]");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Options:");
|
||||
Console.WriteLine(" --prime=<value> starting exponent (p)");
|
||||
Console.WriteLine(" --max-prime=<value> inclusive upper bound for primes from filter files");
|
||||
Console.WriteLine(" --increment=bit|add exponent increment method");
|
||||
Console.WriteLine(" --threads=<value> number of worker threads");
|
||||
Console.WriteLine(" --block-size=<value> values processed per thread batch");
|
||||
Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method");
|
||||
Console.WriteLine(" --residue-max-k=<value> max k for residue Mersenne test (q = 2*p*k + 1)");
|
||||
Console.WriteLine(" --mersenne-device=cpu|gpu Device for Mersenne method (default gpu)");
|
||||
Console.WriteLine(" --primes-device=cpu|gpu Device for prime-scan kernels (default gpu)");
|
||||
Console.WriteLine(" --gpu-prime-batch=<n> Batch size for GPU primality sieve (default 262144)");
|
||||
Console.WriteLine(" --order-device=cpu|gpu Device for order computations (default gpu)");
|
||||
Console.WriteLine(" --ntt=reference|staged GPU NTT backend (default staged)");
|
||||
Console.WriteLine(" --mod-reduction=auto|uint128|mont64|barrett128 staged NTT reduction (default auto)");
|
||||
Console.WriteLine(" --gpu-prime-threads=<value> max concurrent GPU prime checks (default 1)");
|
||||
Console.WriteLine(" --ll-slice=<value> Lucas–Lehmer iterations per slice (default 32)");
|
||||
Console.WriteLine(" --gpu-scan-batch=<value> GPU q-scan batch size (default 2_097_152)");
|
||||
Console.WriteLine(" --order-warmup-limit=<value> Warm-up order candidates (default 5_000_000)");
|
||||
Console.WriteLine(" --rle-blacklist=<path> enable RLE blacklist for p (hard filter up to --rle-hard-max)");
|
||||
Console.WriteLine(" --rle-hard-max=<p> apply RLE blacklist only for p <= this (default ulong.MaxValue = no limit)");
|
||||
Console.WriteLine(" --rle-only-last7=true|false apply RLE only when p % 10 == 7 (default true)");
|
||||
Console.WriteLine(" --zero-hard=<f> hard reject if zero_fraction(p) > f (default off)");
|
||||
Console.WriteLine(" --zero-conj=<f>:<r> hard reject if zero_fraction(p) > f AND max_zero_block >= r (default off)");
|
||||
Console.WriteLine(" --results-dir=<path> directory for results file");
|
||||
Console.WriteLine(" --results-prefix=<text> prefix to prepend to results filename");
|
||||
Console.WriteLine(" --divisor-cycles=<path> divisor cycles data file");
|
||||
Console.WriteLine(" --divisor-cycles-device=cpu|gpu device for cycles generation (default gpu)");
|
||||
Console.WriteLine(" --divisor-cycles-batch=<value> batch size for cycles generation (default 512)");
|
||||
Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation");
|
||||
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor");
|
||||
Console.WriteLine(" --use-order test primality via q order");
|
||||
Console.WriteLine(" --filter-p=<path> process only p from previous run results (required for --mersenne=bydivisor)");
|
||||
Console.WriteLine(" --write-batch-size=<value> overwrite frequency of disk writes (default 100 lines)");
|
||||
Console.WriteLine(" --gcd-filter enable early sieve based on GCD");
|
||||
Console.WriteLine(" --help, -help, --?, -?, /? show this help message");
|
||||
}
|
||||
|
||||
private static string BuildResultsFileName(bool bitInc, int threads, int block, GpuKernelType kernelType, bool useLucasFlag, bool useDivisorFlag, bool useByDivisorFlag, bool mersenneOnGpu, bool useOrder, bool useGcd, NttBackend nttBackend, int gpuPrimeThreads, int llSlice, int gpuScanBatch, ulong warmupLimit, ModReductionMode reduction, string mersenneDevice, string primesDevice, string orderDevice)
|
||||
private static string BuildResultsFileName(bool bitInc, int threads, int block, GpuKernelType kernelType, bool useLucasFlag, bool useDivisorFlag, bool useByDivisorFlag, bool mersenneOnGpu, bool useOrder, bool useGcd, NttBackend nttBackend, int gpuPrimeThreads, int llSlice, int gpuScanBatch, ulong warmupLimit, ModReductionMode reduction, string mersenneDevice, string primesDevice, string orderDevice)
|
||||
{
|
||||
string inc = bitInc ? "bit" : "add";
|
||||
string mers = useDivisorFlag
|
||||
@@ -1127,7 +1083,7 @@ internal static class Program
|
||||
detailedCheck = false;
|
||||
_lastCompositeP = false;
|
||||
|
||||
if (_cliArguments.UseGcdFilter && IsCompositeByGcd(p))
|
||||
if (_cliArguments.UseGcdFilter && p.IsCompositeByGcd())
|
||||
{
|
||||
_lastCompositeP = true;
|
||||
return false;
|
||||
@@ -1151,7 +1107,7 @@ internal static class Program
|
||||
int bitLength;
|
||||
int zeroCountValue;
|
||||
int maxZeroBlockValue;
|
||||
ComputeBitStats(p, out bitLength, out zeroCountValue, out maxZeroBlockValue);
|
||||
p.ComputeBitStats(out bitLength, out zeroCountValue, out maxZeroBlockValue);
|
||||
zf = (double)zeroCountValue / bitLength;
|
||||
if (_cliArguments.ZeroFractionHard >= 0)
|
||||
{
|
||||
@@ -1233,214 +1189,4 @@ internal static class Program
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Convert this to extension method in ULongExtensions if one doesn't already exist. We may already calculate things like these
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeBitStats(ulong value, out int bitLen, out int zeroCount, out int maxZeroBlock)
|
||||
{
|
||||
bitLen = 64 - int.CreateChecked(ulong.LeadingZeroCount(value));
|
||||
zeroCount = 0;
|
||||
maxZeroBlock = 0;
|
||||
if (bitLen <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int msbIndex = (bitLen - 1) >> 3; // 0..7
|
||||
int bitsInTopByte = ((bitLen - 1) & 7) + 1; // 1..8
|
||||
int currentRun = 0;
|
||||
byte inspectedByte;
|
||||
int zeroCountInByte;
|
||||
int prefixZeros;
|
||||
int suffixZeros;
|
||||
int maxZeroRunInByte;
|
||||
int candidate;
|
||||
int byteIndex = msbIndex;
|
||||
|
||||
for (; byteIndex >= 0; byteIndex--)
|
||||
{
|
||||
inspectedByte = (byte)(value >> (byteIndex * 8));
|
||||
if (byteIndex == msbIndex && bitsInTopByte < 8)
|
||||
{
|
||||
// Mask off leading unused bits by setting them to 1 (ignore)
|
||||
inspectedByte |= (byte)(0xFF << bitsInTopByte);
|
||||
}
|
||||
|
||||
// TODO: Replace this byte-by-byte scan with the lookup-table based statistics collector validated in the
|
||||
// BitStats benchmarks so zero runs leverage cached results instead of recomputing per bit.
|
||||
|
||||
zeroCountInByte = ByteZeroCount[inspectedByte];
|
||||
zeroCount += zeroCountInByte;
|
||||
|
||||
if (zeroCountInByte == 8)
|
||||
{
|
||||
currentRun += 8;
|
||||
if (currentRun > maxZeroBlock)
|
||||
{
|
||||
maxZeroBlock = currentRun;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
prefixZeros = BytePrefixZero[inspectedByte];
|
||||
suffixZeros = ByteSuffixZero[inspectedByte];
|
||||
maxZeroRunInByte = ByteMaxZeroRun[inspectedByte];
|
||||
|
||||
candidate = currentRun + prefixZeros;
|
||||
if (candidate > maxZeroBlock)
|
||||
{
|
||||
maxZeroBlock = candidate;
|
||||
}
|
||||
|
||||
if (maxZeroRunInByte > maxZeroBlock)
|
||||
{
|
||||
maxZeroBlock = maxZeroRunInByte;
|
||||
}
|
||||
|
||||
currentRun = suffixZeros;
|
||||
}
|
||||
|
||||
if (currentRun > maxZeroBlock)
|
||||
{
|
||||
maxZeroBlock = currentRun;
|
||||
}
|
||||
}
|
||||
|
||||
// LUTs for fast per-byte zero stats (MSB-first in each byte)
|
||||
private static readonly byte[] ByteZeroCount = new byte[256];
|
||||
private static readonly byte[] BytePrefixZero = new byte[256];
|
||||
private static readonly byte[] ByteSuffixZero = new byte[256];
|
||||
private static readonly byte[] ByteMaxZeroRun = new byte[256];
|
||||
|
||||
static Program()
|
||||
{
|
||||
int valueIndex = 0;
|
||||
int zeros;
|
||||
int pref;
|
||||
int suff;
|
||||
int maxIn;
|
||||
int run;
|
||||
int bit;
|
||||
bool isZero;
|
||||
int boundaryCounter;
|
||||
int boundaryBitIndex;
|
||||
for (; valueIndex < 256; valueIndex++)
|
||||
{
|
||||
zeros = 0;
|
||||
pref = 0;
|
||||
suff = 0;
|
||||
maxIn = 0;
|
||||
run = 0;
|
||||
|
||||
// MSB-first within byte: bit 7 .. bit 0
|
||||
for (bit = 7; bit >= 0; bit--)
|
||||
{
|
||||
isZero = ((valueIndex >> bit) & 1) == 0;
|
||||
if (isZero)
|
||||
{
|
||||
zeros++;
|
||||
run++;
|
||||
if (run > maxIn)
|
||||
{
|
||||
maxIn = run;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
run = 0;
|
||||
}
|
||||
|
||||
if (bit == 7)
|
||||
{
|
||||
// leading zeros (prefix)
|
||||
boundaryCounter = 0;
|
||||
for (boundaryBitIndex = 7; boundaryBitIndex >= 0; boundaryBitIndex--)
|
||||
{
|
||||
if (((valueIndex >> boundaryBitIndex) & 1) == 0)
|
||||
{
|
||||
boundaryCounter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
pref = boundaryCounter;
|
||||
}
|
||||
|
||||
if (bit == 0)
|
||||
{
|
||||
// trailing zeros (suffix)
|
||||
// Reusing boundaryCounter to count trailing zeros after prefix detection above.
|
||||
boundaryCounter = 0;
|
||||
for (boundaryBitIndex = 0; boundaryBitIndex < 8; boundaryBitIndex++)
|
||||
{
|
||||
if (((valueIndex >> boundaryBitIndex) & 1) == 0)
|
||||
{
|
||||
boundaryCounter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
suff = boundaryCounter;
|
||||
}
|
||||
}
|
||||
|
||||
ByteZeroCount[valueIndex] = (byte)zeros;
|
||||
BytePrefixZero[valueIndex] = (byte)pref;
|
||||
ByteSuffixZero[valueIndex] = (byte)suff;
|
||||
ByteMaxZeroRun[valueIndex] = (byte)maxIn;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Convert this to extension method in ULongExtensions if one doesn't already exist
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsCompositeByGcd(ulong p)
|
||||
{
|
||||
// TODO: Can this ever be true on Program's execution path?
|
||||
if (p < 2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ulong m = (ulong)BitOperations.Log2(p);
|
||||
return BinaryGcd(p, m) > 1UL;
|
||||
}
|
||||
|
||||
// TODO: Convert this to extension method in ULongExtensions if one doesn't already exist
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong BinaryGcd(ulong a, ulong b)
|
||||
{
|
||||
// TODO: Are these a & b conditions ever met on Program's execution path?
|
||||
if (a == 0UL)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
|
||||
if (b == 0UL)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
int shift = BitOperations.TrailingZeroCount(a | b);
|
||||
a >>= BitOperations.TrailingZeroCount(a);
|
||||
|
||||
do
|
||||
{
|
||||
b >>= BitOperations.TrailingZeroCount(b);
|
||||
|
||||
if (a > b)
|
||||
{
|
||||
(a, b) = (b, a);
|
||||
}
|
||||
|
||||
b -= a;
|
||||
}
|
||||
while (b != 0UL);
|
||||
|
||||
return a << shift;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,94 @@ public static class ULongExtensions
|
||||
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
||||
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSize - 1);
|
||||
|
||||
private static readonly byte[] BitStatsZeroCount = new byte[256];
|
||||
private static readonly byte[] BitStatsPrefixZero = new byte[256];
|
||||
private static readonly byte[] BitStatsSuffixZero = new byte[256];
|
||||
private static readonly byte[] BitStatsMaxZeroRun = new byte[256];
|
||||
|
||||
static ULongExtensions()
|
||||
{
|
||||
int valueIndex = 0;
|
||||
int zeros;
|
||||
int pref;
|
||||
int suff;
|
||||
int maxIn;
|
||||
int run;
|
||||
int bit;
|
||||
bool isZero;
|
||||
int boundaryCounter;
|
||||
int boundaryBitIndex;
|
||||
for (; valueIndex < 256; valueIndex++)
|
||||
{
|
||||
zeros = 0;
|
||||
pref = 0;
|
||||
suff = 0;
|
||||
maxIn = 0;
|
||||
run = 0;
|
||||
|
||||
for (bit = 7; bit >= 0; bit--)
|
||||
{
|
||||
// MSB-first within each byte: bit 7 down to bit 0.
|
||||
isZero = ((valueIndex >> bit) & 1) == 0;
|
||||
if (isZero)
|
||||
{
|
||||
zeros++;
|
||||
run++;
|
||||
if (run > maxIn)
|
||||
{
|
||||
maxIn = run;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
run = 0;
|
||||
}
|
||||
|
||||
if (bit == 7)
|
||||
{
|
||||
// Leading zeros (prefix).
|
||||
boundaryCounter = 0;
|
||||
for (boundaryBitIndex = 7; boundaryBitIndex >= 0; boundaryBitIndex--)
|
||||
{
|
||||
if (((valueIndex >> boundaryBitIndex) & 1) == 0)
|
||||
{
|
||||
boundaryCounter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
pref = boundaryCounter;
|
||||
}
|
||||
|
||||
if (bit == 0)
|
||||
{
|
||||
// Trailing zeros (suffix).
|
||||
// Reuse boundaryCounter to count trailing zeros after the prefix detection above.
|
||||
boundaryCounter = 0;
|
||||
for (boundaryBitIndex = 0; boundaryBitIndex < 8; boundaryBitIndex++)
|
||||
{
|
||||
if (((valueIndex >> boundaryBitIndex) & 1) == 0)
|
||||
{
|
||||
boundaryCounter++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
suff = boundaryCounter;
|
||||
}
|
||||
}
|
||||
|
||||
BitStatsZeroCount[valueIndex] = (byte)zeros;
|
||||
BitStatsPrefixZero[valueIndex] = (byte)pref;
|
||||
BitStatsSuffixZero[valueIndex] = (byte)suff;
|
||||
BitStatsMaxZeroRun[valueIndex] = (byte)maxIn;
|
||||
}
|
||||
}
|
||||
|
||||
public static ulong CalculateOrder(this ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
@@ -67,6 +155,75 @@ public static class ULongExtensions
|
||||
return 64 - BitOperations.LeadingZeroCount(value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ComputeBitStats(this ulong value, out int bitLen, out int zeroCount, out int maxZeroBlock)
|
||||
{
|
||||
bitLen = 64 - int.CreateChecked(ulong.LeadingZeroCount(value));
|
||||
zeroCount = 0;
|
||||
maxZeroBlock = 0;
|
||||
if (bitLen <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int msbIndex = (bitLen - 1) >> 3;
|
||||
int bitsInTopByte = ((bitLen - 1) & 7) + 1;
|
||||
int currentRun = 0;
|
||||
int byteIndex = msbIndex;
|
||||
int zeroCountInByte;
|
||||
int prefixZeros;
|
||||
int suffixZeros;
|
||||
int maxZeroRunInByte;
|
||||
int candidate;
|
||||
|
||||
for (; byteIndex >= 0; byteIndex--)
|
||||
{
|
||||
byte inspectedByte = (byte)(value >> (byteIndex * 8));
|
||||
if (byteIndex == msbIndex && bitsInTopByte < 8)
|
||||
{
|
||||
// Mask off leading unused bits by setting them to 1 so the statistics ignore them.
|
||||
inspectedByte |= (byte)(0xFF << bitsInTopByte);
|
||||
}
|
||||
|
||||
// TODO: Replace this byte-by-byte scan with the lookup-table based statistics collector validated in the BitStats benchmarks so zero runs leverage cached results instead of recomputing per bit.
|
||||
zeroCountInByte = BitStatsZeroCount[inspectedByte];
|
||||
zeroCount += zeroCountInByte;
|
||||
|
||||
if (zeroCountInByte == 8)
|
||||
{
|
||||
currentRun += 8;
|
||||
if (currentRun > maxZeroBlock)
|
||||
{
|
||||
maxZeroBlock = currentRun;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
prefixZeros = BitStatsPrefixZero[inspectedByte];
|
||||
suffixZeros = BitStatsSuffixZero[inspectedByte];
|
||||
maxZeroRunInByte = BitStatsMaxZeroRun[inspectedByte];
|
||||
|
||||
candidate = currentRun + prefixZeros;
|
||||
if (candidate > maxZeroBlock)
|
||||
{
|
||||
maxZeroBlock = candidate;
|
||||
}
|
||||
|
||||
if (maxZeroRunInByte > maxZeroBlock)
|
||||
{
|
||||
maxZeroBlock = maxZeroRunInByte;
|
||||
}
|
||||
|
||||
currentRun = suffixZeros;
|
||||
}
|
||||
|
||||
if (currentRun > maxZeroBlock)
|
||||
{
|
||||
maxZeroBlock = currentRun;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong MultiplyShiftRight(ulong value, ulong multiplier, int shift)
|
||||
{
|
||||
@@ -131,6 +288,50 @@ public static class ULongExtensions
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCompositeByGcd(this ulong value)
|
||||
{
|
||||
if (value < 2UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ulong exponentLog = (ulong)BitOperations.Log2(value);
|
||||
return value.BinaryGcd(exponentLog) > 1UL;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong BinaryGcd(this ulong a, ulong b)
|
||||
{
|
||||
if (a == 0UL)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
|
||||
if (b == 0UL)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
int shift = BitOperations.TrailingZeroCount(a | b);
|
||||
a >>= BitOperations.TrailingZeroCount(a);
|
||||
|
||||
do
|
||||
{
|
||||
b >>= BitOperations.TrailingZeroCount(b);
|
||||
|
||||
if (a > b)
|
||||
{
|
||||
(a, b) = (b, a);
|
||||
}
|
||||
|
||||
b -= a;
|
||||
}
|
||||
while (b != 0UL);
|
||||
|
||||
return a << shift;
|
||||
}
|
||||
|
||||
// Benchmarks (Mod5ULongBenchmarks) show the direct `% 5` is still cheaper (~0.26 ns vs 0.43 ns), so keep the modulo until a faster lookup is proven.
|
||||
// (Mod8/Mod10 stay masked because they win; Mod5 currently does not.)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
Reference in new issue
Block a user