From 198b31f380b7103b8f4271fe83e424115cd757a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sun, 21 Sep 2025 13:36:43 +0200 Subject: [PATCH] Console logging extended --- EvenPerfectBitScanner/Program.cs | 692 +++++++++--------- ...MersenneNumberDivisorByDivisorGpuTester.cs | 133 ++++ 2 files changed, 479 insertions(+), 346 deletions(-) diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 36dc115..a348b70 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -12,8 +12,8 @@ namespace EvenPerfectBitScanner; internal static class Program { - private static ThreadLocal PrimeTesters = null!; - private static ThreadLocal MersenneTesters = null!; + private static ThreadLocal PrimeTesters = null!; + private static ThreadLocal MersenneTesters = null!; private static ThreadLocal PResidue = null!; // p mod d tracker (per-thread) private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP; private const int WriteBatchSize = 100; @@ -27,13 +27,13 @@ internal static class Program private static int _primeCount; private static bool _primeFoundAfterInit; private static bool _useGcdFilter; - private static bool _useDivisor; - private static bool _useResidueMode; - private static bool _useByDivisorMode; - private static bool _byDivisorPrecheckOnly; - private static UInt128 _divisor; - private static MersenneNumberDivisorGpuTester? _divisorTester; - private static MersenneNumberDivisorByDivisorGpuTester? _byDivisorTester; + private static bool _useDivisor; + private static bool _useResidueMode; + private static bool _useByDivisorMode; + private static bool _byDivisorPrecheckOnly; + private static UInt128 _divisor; + private static MersenneNumberDivisorGpuTester? _divisorTester; + private static MersenneNumberDivisorByDivisorGpuTester? _byDivisorTester; private static ulong? _orderWarmupLimitOverride; private static unsafe delegate* _transformP; private static long _state; @@ -54,18 +54,18 @@ internal static class Program private static double _zeroFracConj = -1.0; // disabled when < 0 private static int _maxZeroConj = -1; // disabled when < 0 - private struct ByDivisorPrimeState - { - internal ulong Prime; - internal ulong AllowedMax; - internal bool Completed; - internal bool Composite; - internal bool DetailedCheck; - } + private struct ByDivisorPrimeState + { + internal ulong Prime; + internal ulong AllowedMax; + internal bool Completed; + internal bool Composite; + internal bool DetailedCheck; + } - private static unsafe void Main(string[] args) - { - ulong currentP = InitialP; + private static unsafe void Main(string[] args) + { + ulong currentP = InitialP; int threadCount = Environment.ProcessorCount; int blockSize = 1; int gpuPrimeThreads = 1; @@ -77,21 +77,21 @@ internal static class Program bool showHelp = false; bool useBitTransform = false; bool useLucas = false; - bool useResidue = false; // M_p test via residue divisors (replaces LL/incremental) - bool useDivisor = false; // M_p divisibility by specific divisor - bool useByDivisor = false; // Iterative divisor scan across primes + bool useResidue = false; // M_p test via residue divisors (replaces LL/incremental) + bool useDivisor = false; // M_p divisibility by specific divisor + bool useByDivisor = false; // Iterative divisor scan across primes UInt128 divisor = UInt128.Zero; // Device routing bool useGpuCycles = true; bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device bool orderOnGpu = true; // controls order computations device int scanBatchSize = 2_097_152, sliceSize = 32; - ulong residueKMax = 5_000_000UL; - string filterFile = string.Empty; - string cyclesPath = DefaultCyclesPath; - int cyclesBatchSize = 512; - bool continueCyclesGeneration = false; - ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; + ulong residueKMax = 5_000_000UL; + string filterFile = string.Empty; + string cyclesPath = DefaultCyclesPath; + int cyclesBatchSize = 512; + bool continueCyclesGeneration = false; + ulong divisorCyclesSearchLimit = PerfectNumberConstants.ExtraDivisorCycleSearchLimit; // NTT backend selection (GPU): reference vs staged for (int i = 0; i < args.Length; i++) @@ -137,19 +137,19 @@ internal static class Program useResidue = true; useLucas = false; } - else if (value.Equals("divisor", StringComparison.OrdinalIgnoreCase)) - { - useDivisor = true; - useLucas = false; - useResidue = false; - } - else if (value.Equals("bydivisor", StringComparison.OrdinalIgnoreCase)) - { - useByDivisor = true; - useLucas = false; - useResidue = false; - useDivisor = false; - } + else if (value.Equals("divisor", StringComparison.OrdinalIgnoreCase)) + { + useDivisor = true; + useLucas = false; + useResidue = false; + } + else if (value.Equals("bydivisor", StringComparison.OrdinalIgnoreCase)) + { + useByDivisor = true; + useLucas = false; + useResidue = false; + useDivisor = false; + } else { kernelType = GpuKernelType.Incremental; @@ -160,21 +160,21 @@ internal static class Program int eq = arg.IndexOf('='); divisor = UInt128.Parse(arg.AsSpan(eq + 1)); } - else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) - { - int eq = arg.IndexOf('='); - if (ulong.TryParse(arg.AsSpan(eq + 1), out var limit)) - { - divisorCyclesSearchLimit = limit; - } - } - else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase)) - { - int eq = arg.IndexOf('='); - if (ulong.TryParse(arg.AsSpan(eq + 1), out var kmax)) - { - residueKMax = kmax; - } + else if (arg.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) + { + int eq = arg.IndexOf('='); + if (ulong.TryParse(arg.AsSpan(eq + 1), out var limit)) + { + divisorCyclesSearchLimit = limit; + } + } + else if (arg.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase)) + { + int eq = arg.IndexOf('='); + if (ulong.TryParse(arg.AsSpan(eq + 1), out var kmax)) + { + residueKMax = kmax; + } } // Replaces --lucas=cpu|gpu; controls device for Lucas and for // incremental/pow2mod scanning @@ -383,19 +383,19 @@ internal static class Program { continueCyclesGeneration = true; } - } + } - if (useByDivisor && string.IsNullOrEmpty(filterFile)) - { - Console.WriteLine("--mersenne=bydivisor requires --filter-p=."); - return; - } + if (useByDivisor && string.IsNullOrEmpty(filterFile)) + { + Console.WriteLine("--mersenne=bydivisor requires --filter-p=."); + return; + } - if (showHelp) - { - PrintHelp(); - return; - } + if (showHelp) + { + PrintHelp(); + return; + } // Apply GPU prime sieve runtime configuration GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads); @@ -444,16 +444,16 @@ internal static class Program Console.WriteLine("Divisor cycles are ready"); - if (useByDivisor) - { - threadCount = 1; - blockSize = 1; - } + if (useByDivisor) + { + threadCount = 1; + blockSize = 1; + } - _useDivisor = useDivisor; - _useResidueMode = useResidue; - _useByDivisorMode = useByDivisor; - _divisor = divisor; + _useDivisor = useDivisor; + _useResidueMode = useResidue; + _useByDivisorMode = useByDivisor; + _divisor = divisor; if (useBitTransform) { _transformP = &TransformPBit; @@ -470,45 +470,45 @@ internal static class Program PrimeTesters = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); // Note: --primes-device controls default device for library kernels; p primality remains CPU here. // Initialize per-thread p residue tracker (Identity model) at currentP - if (!useDivisor && !useByDivisor) - { - MersenneTesters = new ThreadLocal(() => - { - var tester = new MersenneNumberTester( - useIncremental: !useLucas, - useOrderCache: false, - kernelType: kernelType, - useModuloWorkaround: useModuloWorkaround, - useOrder: useOrder, - useGpuLucas: mersenneOnGpu, - useGpuScan: mersenneOnGpu, - useGpuOrder: orderOnGpu, - useResidue: useResidue, - maxK: residueKMax); - if (!useLucas) - { - tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL); - } + if (!useDivisor && !useByDivisor) + { + MersenneTesters = new ThreadLocal(() => +{ + var tester = new MersenneNumberTester( + useIncremental: !useLucas, + useOrderCache: false, + kernelType: kernelType, + useModuloWorkaround: useModuloWorkaround, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu, + useResidue: useResidue, + maxK: residueKMax); + if (!useLucas) + { + tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL); + } - return tester; - }, trackAllValues: true); + return tester; +}, trackAllValues: true); } - else if (useDivisor) - { - if (divisor == UInt128.Zero) - { - MersenneNumberDivisorGpuTester.BuildDivisorCandidates(); - } + else if (useDivisor) + { + if (divisor == UInt128.Zero) + { + MersenneNumberDivisorGpuTester.BuildDivisorCandidates(); + } - _divisorTester = new MersenneNumberDivisorGpuTester(); - } - else if (useByDivisor) - { - _byDivisorTester = new MersenneNumberDivisorByDivisorGpuTester - { - GpuBatchSize = scanBatchSize, - }; - } + _divisorTester = new MersenneNumberDivisorGpuTester(); + } + else if (useByDivisor) + { + _byDivisorTester = new MersenneNumberDivisorByDivisorGpuTester + { + GpuBatchSize = scanBatchSize, + }; + } // Load RLE blacklist (optional) if (!string.IsNullOrEmpty(_rleBlacklistPath)) @@ -534,27 +534,27 @@ internal static class Program Console.WriteLine("Initialization..."); // Compose a results file name that encodes configuration (before opening file) - var builtName = BuildResultsFileName( - useBitTransform, - threadCount, - blockSize, - kernelType, - useLucas, - useDivisor, - useByDivisor, - mersenneOnGpu, - useOrder, - useModuloWorkaround, - _useGcdFilter, - NttGpuMath.GpuTransformBackend, - gpuPrimeThreads, - sliceSize, - scanBatchSize, - _orderWarmupLimitOverride ?? 5_000_000UL, - NttGpuMath.ReductionMode, - mersenneOnGpu ? "gpu" : "cpu", - (GpuContextPool.ForceCpu ? "cpu" : "gpu"), - orderOnGpu ? "gpu" : "cpu"); + var builtName = BuildResultsFileName( + useBitTransform, + threadCount, + blockSize, + kernelType, + useLucas, + useDivisor, + useByDivisor, + mersenneOnGpu, + useOrder, + useModuloWorkaround, + _useGcdFilter, + NttGpuMath.GpuTransformBackend, + gpuPrimeThreads, + sliceSize, + scanBatchSize, + _orderWarmupLimitOverride ?? 5_000_000UL, + NttGpuMath.ReductionMode, + mersenneOnGpu ? "gpu" : "cpu", + (GpuContextPool.ForceCpu ? "cpu" : "gpu"), + orderOnGpu ? "gpu" : "cpu"); if (!string.IsNullOrEmpty(_resultsPrefix)) { @@ -584,13 +584,13 @@ internal static class Program File.WriteAllText(ResultsFileName, $"p,searchedMersenne,detailedCheck,passedAllTests{Environment.NewLine}"); } - bool useFilter = !string.IsNullOrEmpty(filterFile); - HashSet filter = []; - List filterList = []; - ulong maxP = 0UL; - if (useFilter) - { - Console.WriteLine("Loading filter..."); + bool useFilter = !string.IsNullOrEmpty(filterFile); + HashSet filter = []; + List filterList = []; + ulong maxP = 0UL; + if (useFilter) + { + Console.WriteLine("Loading filter..."); ulong[] localFilter = new ulong[1024]; int count = 0; LoadResultsFile(filterFile, (p, detailedCheck, passedAllTests) => @@ -605,7 +605,7 @@ internal static class Program if (count == 1024) { - filter.AddRange(localFilter[..count]); + filter.AddRange(localFilter[..count]); count = 0; Console.WriteLine($"Added {p}"); } @@ -614,26 +614,26 @@ internal static class Program if (count > 0) { - filter.AddRange(localFilter[..count]); - } - filterList = [..filter]; - } + filter.AddRange(localFilter[..count]); + } + filterList = [.. filter]; + } - if (useByDivisor) - { - if (maxP <= 1UL) - { - Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor."); - return; - } + if (useByDivisor) + { + if (maxP <= 1UL) + { + Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor."); + return; + } - _byDivisorTester!.ConfigureFromMaxPrime(maxP); - } + _byDivisorTester!.ConfigureFromMaxPrime(maxP); + } - if (_primeCount >= 2) - { - _primeFoundAfterInit = true; - } + if (_primeCount >= 2) + { + _primeFoundAfterInit = true; + } _outputBuilder = StringBuilderPool.Rent(); @@ -642,19 +642,21 @@ internal static class Program // Configure batch size for GPU primality sieve PrimeTester.GpuBatchSize = gpuPrimeBatch; - if (_useByDivisorMode) - { - RunByDivisorMode(filterList, divisorCyclesSearchLimit); - FlushBuffer(); - StringBuilderPool.Return(_outputBuilder!); - return; - } + Console.WriteLine("Starting scan..."); - if (!useDivisor && !useByDivisor) - { - Console.WriteLine("Warming up orders..."); + if (_useByDivisorMode) + { + RunByDivisorMode(filterList, divisorCyclesSearchLimit); + FlushBuffer(); + StringBuilderPool.Return(_outputBuilder!); + return; + } - threadCount = Math.Max(1, threadCount); + if (!useDivisor && !useByDivisor) + { + Console.WriteLine("Warming up orders..."); + + threadCount = Math.Max(1, threadCount); _ = MersenneTesters.Value; if (!useLucas) { @@ -662,7 +664,6 @@ internal static class Program } } - Console.WriteLine("Starting scan..."); _state = ((long)currentP << 3) | (long)remainder; Task[] tasks = new Task[threadCount]; @@ -730,184 +731,183 @@ internal static class Program Task.WaitAll(tasks); FlushBuffer(); - StringBuilderPool.Return(_outputBuilder!); - } + StringBuilderPool.Return(_outputBuilder!); + } - private static void RunByDivisorMode(List primes, ulong divisorCyclesSearchLimit) - { - if (primes.Count == 0) - { - return; - } + private static void RunByDivisorMode(List primes, ulong divisorCyclesSearchLimit) + { + if (primes.Count == 0) + { + return; + } - primes.Sort(); - List states = new(primes.Count); + List states = new(primes.Count); - _byDivisorPrecheckOnly = true; - foreach (ulong prime in primes) - { - bool passed = IsEvenPerfectCandidate(prime, divisorCyclesSearchLimit, out bool searched, out bool detailed); - if (!passed) - { - PrintResult(prime, searched, detailed, false); - continue; - } + _byDivisorPrecheckOnly = true; + foreach (ulong prime in primes) + { + bool passed = IsEvenPerfectCandidate(prime, divisorCyclesSearchLimit, out bool searched, out bool detailed); + if (!passed) + { + PrintResult(prime, searched, detailed, false); + continue; + } - ulong allowedMax = _byDivisorTester!.GetAllowedMaxDivisor(prime); - if (allowedMax < 3UL) - { - PrintResult(prime, searchedMersenne: true, detailedCheck: true, passedAllTests: true); - continue; - } + ulong allowedMax = _byDivisorTester!.GetAllowedMaxDivisor(prime); + if (allowedMax < 3UL) + { + PrintResult(prime, searchedMersenne: true, detailedCheck: true, passedAllTests: true); + continue; + } - states.Add(new ByDivisorPrimeState - { - Prime = prime, - AllowedMax = allowedMax, - Completed = false, - Composite = false, - DetailedCheck = false, - }); - } + states.Add(new ByDivisorPrimeState + { + Prime = prime, + AllowedMax = allowedMax, + Completed = false, + Composite = false, + DetailedCheck = false, + }); + } - _byDivisorPrecheckOnly = false; + _byDivisorPrecheckOnly = false; - if (states.Count == 0) - { - return; - } + if (states.Count == 0) + { + return; + } - using var session = _byDivisorTester!.CreateDivisorSession(); - byte[] hitsBuffer = ArrayPool.Shared.Rent(states.Count); - ulong[] primeBuffer = ArrayPool.Shared.Rent(states.Count); - int[] indexBuffer = ArrayPool.Shared.Rent(states.Count); + using var session = _byDivisorTester!.CreateDivisorSession(); + byte[] hitsBuffer = ArrayPool.Shared.Rent(states.Count); + ulong[] primeBuffer = ArrayPool.Shared.Rent(states.Count); + int[] indexBuffer = ArrayPool.Shared.Rent(states.Count); - try - { - ulong divisor = 3UL; - ulong divisorLimit = _byDivisorTester!.DivisorLimit; + try + { + ulong divisor = 3UL; + ulong divisorLimit = _byDivisorTester!.DivisorLimit; - while (true) - { - bool anyPending = false; - int activeCount = 0; + while (true) + { + bool anyPending = false; + int activeCount = 0; - for (int i = 0; i < states.Count; i++) - { - var state = states[i]; - if (state.Completed) - { - continue; - } + for (int i = 0; i < states.Count; i++) + { + var state = states[i]; + if (state.Completed) + { + continue; + } - if (state.AllowedMax < divisor) - { - state.Completed = true; - state.DetailedCheck = true; - states[i] = state; - PrintResult(state.Prime, searchedMersenne: true, detailedCheck: true, passedAllTests: true); - continue; - } + if (state.AllowedMax < divisor) + { + state.Completed = true; + state.DetailedCheck = true; + states[i] = state; + PrintResult(state.Prime, searchedMersenne: true, detailedCheck: true, passedAllTests: true); + continue; + } - anyPending = true; + anyPending = true; - if (divisor > divisorLimit) - { - states[i] = state; - continue; - } + if (divisor > divisorLimit) + { + states[i] = state; + continue; + } - primeBuffer[activeCount] = state.Prime; - indexBuffer[activeCount] = i; - activeCount++; - states[i] = state; - } + primeBuffer[activeCount] = state.Prime; + indexBuffer[activeCount] = i; + activeCount++; + states[i] = state; + } - if (!anyPending || divisor > divisorLimit) - { - break; - } + if (!anyPending || divisor > divisorLimit) + { + break; + } - if (activeCount == 0) - { - ulong nextDivisor = divisor + 2UL; - if (nextDivisor <= divisor) - { - break; - } + if (activeCount == 0) + { + ulong nextDivisor = divisor + 2UL; + if (nextDivisor <= divisor) + { + break; + } - divisor = nextDivisor; - continue; - } + divisor = nextDivisor; + continue; + } - session.CheckDivisor(divisor, primeBuffer.AsSpan(0, activeCount), hitsBuffer.AsSpan(0, activeCount)); + session.CheckDivisor(divisor, primeBuffer.AsSpan(0, activeCount), hitsBuffer.AsSpan(0, activeCount)); - for (int i = 0; i < activeCount; i++) - { - if (hitsBuffer[i] == 0) - { - continue; - } + for (int i = 0; i < activeCount; i++) + { + if (hitsBuffer[i] == 0) + { + continue; + } - int index = indexBuffer[i]; - var state = states[index]; - if (state.Completed) - { - continue; - } + int index = indexBuffer[i]; + var state = states[index]; + if (state.Completed) + { + continue; + } - state.Completed = true; - state.Composite = true; - state.DetailedCheck = true; - states[index] = state; - PrintResult(state.Prime, searchedMersenne: true, detailedCheck: true, passedAllTests: false); - } + state.Completed = true; + state.Composite = true; + state.DetailedCheck = true; + states[index] = state; + PrintResult(state.Prime, searchedMersenne: true, detailedCheck: true, passedAllTests: false); + } - bool remaining = false; - for (int i = 0; i < states.Count; i++) - { - if (!states[i].Completed) - { - remaining = true; - break; - } - } + bool remaining = false; + for (int i = 0; i < states.Count; i++) + { + if (!states[i].Completed) + { + remaining = true; + break; + } + } - if (!remaining) - { - break; - } + if (!remaining) + { + break; + } - ulong next = divisor + 2UL; - if (next <= divisor) - { - break; - } + ulong next = divisor + 2UL; + if (next <= divisor) + { + break; + } - divisor = next; - } + divisor = next; + } - for (int i = 0; i < states.Count; i++) - { - var state = states[i]; - if (state.Completed) - { - continue; - } + for (int i = 0; i < states.Count; i++) + { + var state = states[i]; + if (state.Completed) + { + continue; + } - state.Completed = true; - state.DetailedCheck = divisor > state.AllowedMax; - states[i] = state; - PrintResult(state.Prime, searchedMersenne: true, detailedCheck: state.DetailedCheck, passedAllTests: true); - } - } - finally - { - ArrayPool.Shared.Return(indexBuffer, clearArray: true); - ArrayPool.Shared.Return(primeBuffer, clearArray: true); - ArrayPool.Shared.Return(hitsBuffer, clearArray: true); - } - } + state.Completed = true; + state.DetailedCheck = divisor > state.AllowedMax; + states[i] = state; + PrintResult(state.Prime, searchedMersenne: true, detailedCheck: state.DetailedCheck, passedAllTests: true); + } + } + finally + { + ArrayPool.Shared.Return(indexBuffer, clearArray: true); + ArrayPool.Shared.Return(primeBuffer, clearArray: true); + ArrayPool.Shared.Return(hitsBuffer, clearArray: true); + } + } private static unsafe void LoadResultsFile(string resultsFileName, Action lineProcessorAction) { @@ -969,7 +969,7 @@ internal static class Program Console.WriteLine(" --increment=bit|add exponent increment method"); Console.WriteLine(" --threads= number of worker threads"); Console.WriteLine(" --block-size= values processed per thread batch"); - Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method"); + Console.WriteLine(" --mersenne=pow2mod|incremental|lucas|residue|divisor|bydivisor Mersenne test method"); Console.WriteLine(" --divisor= optional divisor for --mersenne=divisor mode"); Console.WriteLine(" --residue-max-k= max k for residue Mersenne test (q = 2*p*k + 1)"); Console.WriteLine(" --mersenne-device=cpu|gpu Device for Mersenne method (default gpu)"); @@ -998,22 +998,22 @@ internal static class Program Console.WriteLine(" --use-order test primality via q order"); Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU"); // mod-automaton removed - Console.WriteLine(" --filter-p= process only p from previous run results (required for --mersenne=bydivisor)"); + Console.WriteLine(" --filter-p= process only p from previous run results (required for --mersenne=bydivisor)"); Console.WriteLine(" --write-batch-size= 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 useModWorkaround, 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 useModWorkaround, 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 - ? "divisor" - : (useLucasFlag - ? "lucas" - : (useByDivisorFlag - ? "bydivisor" - : (kernelType == GpuKernelType.Pow2Mod ? "pow2mod" : "incremental"))); + string mers = useDivisorFlag + ? "divisor" + : (useLucasFlag + ? "lucas" + : (useByDivisorFlag + ? "bydivisor" + : (kernelType == GpuKernelType.Pow2Mod ? "pow2mod" : "incremental"))); string ntt = nttBackend == NttBackend.Staged ? "staged" : "reference"; string red = reduction switch { ModReductionMode.Mont64 => "mont64", ModReductionMode.Barrett128 => "barrett128", ModReductionMode.GpuUInt128 => "uint128", _ => "auto" }; string order = useOrder ? "order-on" : "order-off"; @@ -1353,23 +1353,23 @@ internal static class Program return false; } - searchedMersenne = true; - if (_useByDivisorMode) - { - if (_byDivisorPrecheckOnly) - { - searchedMersenne = false; - detailedCheck = false; - return true; - } + searchedMersenne = true; + if (_useByDivisorMode) + { + if (_byDivisorPrecheckOnly) + { + searchedMersenne = false; + detailedCheck = false; + return true; + } - return _byDivisorTester!.IsPrime(p, out detailedCheck); - } + return _byDivisorTester!.IsPrime(p, out detailedCheck); + } - if (_useDivisor) - { - return _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out detailedCheck); - } + if (_useDivisor) + { + return _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out detailedCheck); + } detailedCheck = MersenneTesters.Value!.IsMersennePrime(p); return detailedCheck; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index d1c6410..ac6658c 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -17,9 +17,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester private ulong _lastStatusDivisor; private readonly ConcurrentDictionary, ArrayView>> _kernelCache = new(); + private readonly ConcurrentDictionary, ArrayView>> _kernelByPrimeCache = new(); private Action, ArrayView> GetKernel(Accelerator accelerator) => _kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView>(CheckKernel)); + private Action, ArrayView> GetKernelByPrime(Accelerator accelerator) => + _kernelByPrimeCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel, ArrayView>(CheckKernelByPrime)); public int GpuBatchSize { @@ -110,6 +113,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester divisors[batchSize++] = current; ulong next = current + 2UL; + ReportStatus(lastProcessed); if (next <= current) { reachedEndInBatch = true; @@ -153,6 +157,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester } } + ReportStatus(lastProcessed); if (composite) { break; @@ -232,6 +237,79 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester hits[index] = pow == 1UL ? (byte)1 : (byte)0; } + public sealed class DivisorScanSession : IDisposable + { + private readonly MersenneNumberDivisorByDivisorGpuTester _owner; + private readonly GpuContextPool.GpuContextLease _lease; + private readonly Accelerator _accelerator; + private readonly Action, ArrayView> _kernel; + private readonly MemoryBuffer1D _primesBuffer; + private readonly MemoryBuffer1D _hitsBuffer; + private readonly ulong[] _primesHost; + private readonly byte[] _hitsHost; + private bool _disposed; + + internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) + { + _owner = owner; + _lease = GpuContextPool.RentPreferred(preferCpu: false); + _accelerator = _lease.Accelerator; + _kernel = owner.GetKernelByPrime(_accelerator); + _primesBuffer = _accelerator.Allocate1D(owner._gpuBatchSize); + _hitsBuffer = _accelerator.Allocate1D(owner._gpuBatchSize); + _primesHost = ArrayPool.Shared.Rent(owner._gpuBatchSize); + _hitsHost = ArrayPool.Shared.Rent(owner._gpuBatchSize); + } + + public void CheckDivisor(ulong divisor, ReadOnlySpan primes, Span hits) + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(DivisorScanSession)); + } + + if (primes.Length == 0) + { + return; + } + + int offset = 0; + while (offset < primes.Length) + { + int batchSize = Math.Min(_owner._gpuBatchSize, primes.Length - offset); + Span primeSpan = _primesHost.AsSpan(0, batchSize); + primes.Slice(offset, batchSize).CopyTo(primeSpan); + + var primesView = _primesBuffer.View.SubView(0, batchSize); + var hitsView = _hitsBuffer.View.SubView(0, batchSize); + + primesView.CopyFromCPU(ref MemoryMarshal.GetReference(primeSpan), batchSize); + _kernel(batchSize, divisor, primesView, hitsView); + _accelerator.Synchronize(); + + Span hitSpan = _hitsHost.AsSpan(0, batchSize); + hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitSpan), batchSize); + hitSpan.CopyTo(hits.Slice(offset, batchSize)); + offset += batchSize; + } + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + ArrayPool.Shared.Return(_primesHost, clearArray: true); + ArrayPool.Shared.Return(_hitsHost, clearArray: true); + _primesBuffer.Dispose(); + _hitsBuffer.Dispose(); + _lease.Dispose(); + } + } + private static ulong Pow2Mod(ulong exponent, ulong modulus) { if (modulus <= 1UL) @@ -293,5 +371,60 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester return result; } + + private static void CheckKernelByPrime(Index1D index, ulong divisor, ArrayView primes, ArrayView hits) + { + ulong prime = primes[index]; + if (divisor <= 1UL || (divisor & 1UL) == 0UL) + { + hits[index] = 0; + return; + } + + ulong pow = Pow2Mod(prime, divisor); + hits[index] = pow == 1UL ? (byte)1 : (byte)0; + } + + public ulong DivisorLimit + { + get + { + lock (_sync) + { + if (!_isConfigured) + { + throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + } + + return _divisorLimit; + } + } + } + + public ulong GetAllowedMaxDivisor(ulong prime) + { + lock (_sync) + { + if (!_isConfigured) + { + throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + } + + return ComputeAllowedMaxDivisor(prime); + } + } + + public DivisorScanSession CreateDivisorSession() + { + lock (_sync) + { + if (!_isConfigured) + { + throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + } + + return new DivisorScanSession(this); + } + } }