Merge pull request #201 from mlemanczyk/codex/remove-todos-from-latest-commit
Restore documentation for handler and bit statistics invariants
This commit is contained in:
5 files changed
+303
-350
No files matched your search
@@ -63,8 +63,8 @@ public class ProgramTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void Gcd_filter_detects_some_composites()
|
public void Gcd_filter_detects_some_composites()
|
||||||
{
|
{
|
||||||
Program.IsCompositeByGcd(15UL).Should().BeTrue();
|
15UL.IsCompositeByGcd().Should().BeTrue();
|
||||||
Program.IsCompositeByGcd(5UL).Should().BeFalse();
|
5UL.IsCompositeByGcd().Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
using System.Buffers.Text;
|
using System.Buffers.Text;
|
||||||
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner;
|
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 static class CalculationResultHandler
|
||||||
internal struct CalculationResultHandler
|
|
||||||
{
|
{
|
||||||
private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||||
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
||||||
internal const int DefaultWriteBatchSize = 100;
|
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 static StringBuilder? _outputBuilder;
|
||||||
private readonly object _fileWriteSync;
|
private static TextFileWriter? _resultsWriter;
|
||||||
private StringBuilder? _outputBuilder;
|
private static int _consoleCounter;
|
||||||
private TextFileWriter? _resultsWriter;
|
private static int _writeIndex;
|
||||||
private int _consoleCounter;
|
private static int _writeBatchSize;
|
||||||
private int _writeIndex;
|
private static string _resultsFileName = string.Empty;
|
||||||
private readonly int _writeBatchSize;
|
private static int _primeCount;
|
||||||
private readonly string _resultsFileName;
|
private static bool _primeFoundAfterInit;
|
||||||
private int _primeCount;
|
|
||||||
private bool _primeFoundAfterInit;
|
|
||||||
|
|
||||||
private readonly bool IsInitialized => _sync is not null;
|
internal static string ResultsFileName => _resultsFileName;
|
||||||
private readonly bool HasFileSync => _fileWriteSync is not null;
|
|
||||||
|
|
||||||
internal CalculationResultHandler(string resultsFileName, int writeBatchSize)
|
internal static void Initialize(string resultsFileName, int writeBatchSize)
|
||||||
{
|
{
|
||||||
_sync = new object();
|
Dispose();
|
||||||
_fileWriteSync = new object();
|
|
||||||
_outputBuilder = null;
|
_outputBuilder = null;
|
||||||
_resultsWriter = null;
|
_resultsWriter = null;
|
||||||
_consoleCounter = 0;
|
_consoleCounter = 0;
|
||||||
@@ -42,36 +42,22 @@ internal struct CalculationResultHandler
|
|||||||
_primeFoundAfterInit = false;
|
_primeFoundAfterInit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal readonly string ResultsFileName => _resultsFileName;
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void InitializeOutputBuffer()
|
internal static void InitializeOutputBuffer()
|
||||||
{
|
{
|
||||||
// Program flow ensures the handler is initialized before this call. Do not uncomment the guard below.
|
// Program flow ensures Initialize(...) runs before this call. Do not add null guards here.
|
||||||
// if (!IsInitialized)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
_outputBuilder = StringBuilderPool.Rent();
|
_outputBuilder = StringBuilderPool.Rent();
|
||||||
_writeIndex = 0;
|
_writeIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void ReleaseOutputBuffer()
|
internal static void ReleaseOutputBuffer()
|
||||||
{
|
{
|
||||||
// Program flow ensures the handler is initialized before this call. Do not uncomment the guard below.
|
// Program flow ensures InitializeOutputBuffer() populated the builder. Do not add null checks here.
|
||||||
// if (!IsInitialized)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
StringBuilder builder = _outputBuilder!;
|
StringBuilder builder = _outputBuilder!;
|
||||||
// Program flow guarantees the output builder is populated when releasing. Do not uncomment the guard below.
|
// Program flow guarantees the output builder is populated when releasing. Do not add guard checks here.
|
||||||
// if (builder is null)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
_outputBuilder = null;
|
_outputBuilder = null;
|
||||||
_writeIndex = 0;
|
_writeIndex = 0;
|
||||||
@@ -79,15 +65,11 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void CreateResultsFileWithHeader()
|
internal static void CreateResultsFileWithHeader()
|
||||||
{
|
{
|
||||||
// Program flow guarantees the file synchronization primitives are available here. Do not uncomment the guard below.
|
// Program flow guarantees FileWriteSync is available here. Do not add guard checks for the lock object.
|
||||||
// if (!HasFileSync)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
lock (_fileWriteSync)
|
lock (FileWriteSync)
|
||||||
{
|
{
|
||||||
_resultsWriter?.Dispose();
|
_resultsWriter?.Dispose();
|
||||||
var writer = new TextFileWriter(_resultsFileName, FileMode.Create);
|
var writer = new TextFileWriter(_resultsFileName, FileMode.Create);
|
||||||
@@ -97,7 +79,7 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void RegisterExistingResult(bool detailedCheck, bool passedAllTests)
|
internal static void RegisterExistingResult(bool detailedCheck, bool passedAllTests)
|
||||||
{
|
{
|
||||||
if (!detailedCheck || !passedAllTests)
|
if (!detailedCheck || !passedAllTests)
|
||||||
{
|
{
|
||||||
@@ -113,13 +95,9 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
internal static 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.
|
// Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
|
||||||
// if (!IsInitialized)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Span<byte> stackBuffer = stackalloc byte[128];
|
Span<byte> stackBuffer = stackalloc byte[128];
|
||||||
byte[]? rentedBuffer = null;
|
byte[]? rentedBuffer = null;
|
||||||
@@ -174,17 +152,13 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void FlushBuffer()
|
internal static void FlushBuffer()
|
||||||
{
|
{
|
||||||
// Program flow ensures the handler is initialized before this call. Do not uncomment the guard below.
|
// Program flow ensures InitializeOutputBuffer() prepared the buffer. Do not add null checks here.
|
||||||
// if (!IsInitialized)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
StringBuilder? builderToFlush;
|
StringBuilder? builderToFlush;
|
||||||
|
|
||||||
lock (_sync)
|
lock (Sync)
|
||||||
{
|
{
|
||||||
if (_writeIndex == 0 || _outputBuilder is null || _outputBuilder.Length == 0)
|
if (_writeIndex == 0 || _outputBuilder is null || _outputBuilder.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -200,15 +174,11 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void Dispose()
|
internal static void Dispose()
|
||||||
{
|
{
|
||||||
// Program flow guarantees the file synchronization primitives are available here. Do not uncomment the guard below.
|
// Program flow guarantees FileWriteSync is ready when disposing. Do not add guard checks for the lock object.
|
||||||
// if (!HasFileSync)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
lock (_fileWriteSync)
|
lock (FileWriteSync)
|
||||||
{
|
{
|
||||||
_resultsWriter?.Dispose();
|
_resultsWriter?.Dispose();
|
||||||
_resultsWriter = null;
|
_resultsWriter = null;
|
||||||
@@ -216,7 +186,7 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private bool TryAcquireConsoleSlot(bool primeCandidate, out bool primeFlag)
|
private static bool TryAcquireConsoleSlot(bool primeCandidate, out bool primeFlag)
|
||||||
{
|
{
|
||||||
int consoleInterval = PerfectNumberConstants.ConsoleInterval;
|
int consoleInterval = PerfectNumberConstants.ConsoleInterval;
|
||||||
int counterValue = Interlocked.Increment(ref _consoleCounter);
|
int counterValue = Interlocked.Increment(ref _consoleCounter);
|
||||||
@@ -235,11 +205,11 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private StringBuilder? AppendToOutputBuffer(ReadOnlySpan<char> text)
|
private static StringBuilder? AppendToOutputBuffer(ReadOnlySpan<char> text)
|
||||||
{
|
{
|
||||||
StringBuilder? builderToFlush = null;
|
StringBuilder? builderToFlush = null;
|
||||||
|
|
||||||
lock (_sync)
|
lock (Sync)
|
||||||
{
|
{
|
||||||
StringBuilder outputBuilder = _outputBuilder!;
|
StringBuilder outputBuilder = _outputBuilder!;
|
||||||
_ = outputBuilder.Append(text);
|
_ = outputBuilder.Append(text);
|
||||||
@@ -261,7 +231,7 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private readonly int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||||
{
|
{
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
@@ -320,19 +290,13 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void FlushBuffer(StringBuilder builderToFlush)
|
private static void FlushBuffer(StringBuilder builderToFlush)
|
||||||
{
|
{
|
||||||
// Program flow guarantees the file synchronization primitives are available here. Do not uncomment the guard below.
|
// Program flow guarantees FileWriteSync is ready here as well. Do not add guard checks for the lock object.
|
||||||
// if (!HasFileSync)
|
|
||||||
// {
|
|
||||||
// builderToFlush.Clear();
|
|
||||||
// StringBuilderPool.Return(builderToFlush);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lock (_fileWriteSync)
|
lock (FileWriteSync)
|
||||||
{
|
{
|
||||||
TextFileWriter writer = EnsureResultsWriter();
|
TextFileWriter writer = EnsureResultsWriter();
|
||||||
writer.Write(builderToFlush);
|
writer.Write(builderToFlush);
|
||||||
@@ -346,7 +310,7 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private TextFileWriter EnsureResultsWriter()
|
private static TextFileWriter EnsureResultsWriter()
|
||||||
{
|
{
|
||||||
TextFileWriter? writer = _resultsWriter;
|
TextFileWriter? writer = _resultsWriter;
|
||||||
if (writer is not null)
|
if (writer is not null)
|
||||||
@@ -360,7 +324,7 @@ internal struct CalculationResultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private bool UpdatePrimeTracking(bool passedAllTests, bool lastWasComposite)
|
private static bool UpdatePrimeTracking(bool passedAllTests, bool lastWasComposite)
|
||||||
{
|
{
|
||||||
if (passedAllTests)
|
if (passedAllTests)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
@@ -560,4 +561,45 @@ internal readonly struct CliArguments
|
|||||||
zeroFractionConjecture,
|
zeroFractionConjecture,
|
||||||
maxZeroConjecture);
|
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 readonly Optimized PrimeIterator = new();
|
||||||
private static ulong _byDivisorStartPrime;
|
private static ulong _byDivisorStartPrime;
|
||||||
private static CliArguments _cliArguments;
|
private static CliArguments _cliArguments;
|
||||||
private static CalculationResultHandler _resultHandler;
|
|
||||||
private static int _testTargetPrimeCount;
|
private static int _testTargetPrimeCount;
|
||||||
private static int _testProcessedPrimeCount;
|
private static int _testProcessedPrimeCount;
|
||||||
private const string TestTimeFileName = "even_perfect_test_time.txt";
|
private const string TestTimeFileName = "even_perfect_test_time.txt";
|
||||||
@@ -60,7 +59,7 @@ internal static class Program
|
|||||||
|
|
||||||
if (_cliArguments.ShowHelp)
|
if (_cliArguments.ShowHelp)
|
||||||
{
|
{
|
||||||
PrintHelp();
|
CliArguments.PrintHelp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +287,7 @@ internal static class Program
|
|||||||
? builtName
|
? builtName
|
||||||
: Path.Combine(_cliArguments.ResultsDirectory!, builtName);
|
: Path.Combine(_cliArguments.ResultsDirectory!, builtName);
|
||||||
|
|
||||||
_resultHandler = new CalculationResultHandler(resultsFileName, _cliArguments.WriteBatchSize);
|
CalculationResultHandler.Initialize(resultsFileName, _cliArguments.WriteBatchSize);
|
||||||
var dir = Path.GetDirectoryName(resultsFileName);
|
var dir = Path.GetDirectoryName(resultsFileName);
|
||||||
if (!string.IsNullOrEmpty(dir))
|
if (!string.IsNullOrEmpty(dir))
|
||||||
{
|
{
|
||||||
@@ -304,7 +303,7 @@ internal static class Program
|
|||||||
Console.WriteLine("Processing previous results...");
|
Console.WriteLine("Processing previous results...");
|
||||||
LoadResultsFile(resultsFileName, (p, detailedCheck, passedAllTests) =>
|
LoadResultsFile(resultsFileName, (p, detailedCheck, passedAllTests) =>
|
||||||
{
|
{
|
||||||
_resultHandler.RegisterExistingResult(detailedCheck, passedAllTests);
|
CalculationResultHandler.RegisterExistingResult(detailedCheck, passedAllTests);
|
||||||
|
|
||||||
if (previousResults is not null)
|
if (previousResults is not null)
|
||||||
{
|
{
|
||||||
@@ -314,7 +313,7 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_resultHandler.CreateResultsFileWithHeader();
|
CalculationResultHandler.CreateResultsFileWithHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool useFilter = !testMode && !string.IsNullOrEmpty(filterFile);
|
bool useFilter = !testMode && !string.IsNullOrEmpty(filterFile);
|
||||||
@@ -412,13 +411,12 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
_testTargetPrimeCount = testPrimeCandidateLimit;
|
_testTargetPrimeCount = testPrimeCandidateLimit;
|
||||||
}
|
}
|
||||||
_resultHandler.InitializeOutputBuffer();
|
CalculationResultHandler.InitializeOutputBuffer();
|
||||||
|
|
||||||
// Limit GPU concurrency only for prime checks (LL/NTT & GPU order scans).
|
// Limit GPU concurrency only for prime checks (LL/NTT & GPU order scans).
|
||||||
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
|
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
|
||||||
// Configure batch size for GPU primality sieve
|
// Configure batch size for GPU primality sieve
|
||||||
PrimeTester.GpuBatchSize = gpuPrimeBatch;
|
PrimeTester.GpuBatchSize = gpuPrimeBatch;
|
||||||
CalculationResultHandler resultHandler = _resultHandler;
|
|
||||||
|
|
||||||
Stopwatch? stopwatch = null;
|
Stopwatch? stopwatch = null;
|
||||||
if (testMode)
|
if (testMode)
|
||||||
@@ -437,10 +435,10 @@ internal static class Program
|
|||||||
_byDivisorStartPrime,
|
_byDivisorStartPrime,
|
||||||
static () => _lastCompositeP = true,
|
static () => _lastCompositeP = true,
|
||||||
static () => _lastCompositeP = false,
|
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);
|
threadCount);
|
||||||
_resultHandler.FlushBuffer();
|
CalculationResultHandler.FlushBuffer();
|
||||||
_resultHandler.ReleaseOutputBuffer();
|
CalculationResultHandler.ReleaseOutputBuffer();
|
||||||
if (stopwatch is not null)
|
if (stopwatch is not null)
|
||||||
{
|
{
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
@@ -495,7 +493,7 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
p = buffer[j];
|
p = buffer[j];
|
||||||
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
|
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
|
||||||
resultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -518,7 +516,7 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
|
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)
|
if (p == maxP)
|
||||||
{
|
{
|
||||||
@@ -548,8 +546,8 @@ internal static class Program
|
|||||||
reportTestTime = true;
|
reportTestTime = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_resultHandler.FlushBuffer();
|
CalculationResultHandler.FlushBuffer();
|
||||||
_resultHandler.ReleaseOutputBuffer();
|
CalculationResultHandler.ReleaseOutputBuffer();
|
||||||
|
|
||||||
if (reportTestTime)
|
if (reportTestTime)
|
||||||
{
|
{
|
||||||
@@ -558,7 +556,7 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_resultHandler.Dispose();
|
CalculationResultHandler.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,48 +714,6 @@ 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 inc = bitInc ? "bit" : "add";
|
||||||
@@ -1127,7 +1083,7 @@ internal static class Program
|
|||||||
detailedCheck = false;
|
detailedCheck = false;
|
||||||
_lastCompositeP = false;
|
_lastCompositeP = false;
|
||||||
|
|
||||||
if (_cliArguments.UseGcdFilter && IsCompositeByGcd(p))
|
if (_cliArguments.UseGcdFilter && p.IsCompositeByGcd())
|
||||||
{
|
{
|
||||||
_lastCompositeP = true;
|
_lastCompositeP = true;
|
||||||
return false;
|
return false;
|
||||||
@@ -1151,7 +1107,7 @@ internal static class Program
|
|||||||
int bitLength;
|
int bitLength;
|
||||||
int zeroCountValue;
|
int zeroCountValue;
|
||||||
int maxZeroBlockValue;
|
int maxZeroBlockValue;
|
||||||
ComputeBitStats(p, out bitLength, out zeroCountValue, out maxZeroBlockValue);
|
p.ComputeBitStats(out bitLength, out zeroCountValue, out maxZeroBlockValue);
|
||||||
zf = (double)zeroCountValue / bitLength;
|
zf = (double)zeroCountValue / bitLength;
|
||||||
if (_cliArguments.ZeroFractionHard >= 0)
|
if (_cliArguments.ZeroFractionHard >= 0)
|
||||||
{
|
{
|
||||||
@@ -1233,214 +1189,4 @@ internal static class Program
|
|||||||
return false;
|
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 ulong Pow2WindowFallbackThreshold = 32UL;
|
||||||
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSize - 1);
|
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)
|
public static ulong CalculateOrder(this ulong q)
|
||||||
{
|
{
|
||||||
if (q <= 2UL)
|
if (q <= 2UL)
|
||||||
@@ -67,6 +155,75 @@ public static class ULongExtensions
|
|||||||
return 64 - BitOperations.LeadingZeroCount(value);
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong MultiplyShiftRight(ulong value, ulong multiplier, int shift)
|
public static ulong MultiplyShiftRight(ulong value, ulong multiplier, int shift)
|
||||||
{
|
{
|
||||||
@@ -131,6 +288,50 @@ public static class ULongExtensions
|
|||||||
return true;
|
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.
|
// 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.)
|
// (Mod8/Mod10 stay masked because they win; Mod5 currently does not.)
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
Reference in new issue
Block a user