From c5c42b0c19a41f5513681221760b1d0fb466a9a1 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Fri, 19 Dec 2025 20:01:45 +0100 Subject: [PATCH] MersenneNumberTesterTemplate made dependent on the calculation method to eliminate unnecessary branching. --- EvenPerfectBitScanner.Tests/ProgramTests.cs | 29 +- EvenPerfectBitScanner/Program.cs | 374 +++++++++-- .../MersenneNumberTesterTests.cs | 6 +- .../MersenneResidueModeTests.cs | 10 +- .../MersenneNumberTesterTemplate.cs | 608 +++++++++--------- 5 files changed, 631 insertions(+), 396 deletions(-) diff --git a/EvenPerfectBitScanner.Tests/ProgramTests.cs b/EvenPerfectBitScanner.Tests/ProgramTests.cs index 8745a19..ac76716 100644 --- a/EvenPerfectBitScanner.Tests/ProgramTests.cs +++ b/EvenPerfectBitScanner.Tests/ProgramTests.cs @@ -227,7 +227,7 @@ public class ProgramTests var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterCpu(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true)); + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterForResidueCalculationMethodForCpu( maxK: 1_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); @@ -260,9 +260,8 @@ public class ProgramTests var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterCpu( - useIncremental: true, - useResidue: true, + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterForResidueCalculationMethodForCpu( + maxK: 1_024UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); @@ -297,9 +296,8 @@ public class ProgramTests var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterCpu( - useIncremental: true, - useResidue: true, + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterForResidueCalculationMethodForCpu( + maxK: 5_000_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); @@ -333,9 +331,8 @@ public class ProgramTests var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterCpu( - useIncremental: true, - useResidue: true, + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterForResidueCalculationMethodForCpu( + maxK: 5_000_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); @@ -370,11 +367,10 @@ public class ProgramTests var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterCpu( - useIncremental: true, + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterForResidueCalculationMethodForCpu( + useGpuScan: false, useGpuOrder: false, - useResidue: true, maxK: 1_024UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); @@ -406,12 +402,11 @@ public class ProgramTests var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; - mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterCpu( - useIncremental: false, + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterForResidueCalculationMethodForCpu( + kernelType: configuration.KernelType, useGpuScan: true, useGpuOrder: configuration.UseGpuOrder, - useResidue: true, maxK: configuration.ResidueMaxK), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); @@ -493,7 +488,7 @@ public class ProgramTests var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!; - mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterCpu(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true)); + mersenneField.SetValue(null, new ThreadLocal(() => new MersenneNumberTesterForIncrementalCalculationMethodForCpu( maxK: 1_000UL), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index de91414..55a7adf 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -29,6 +29,7 @@ private static ThreadLocal? MersenneTesters; private static int _testProcessedPrimeCount; private static ComputationDevice _mersenneDevice; private static ComputationDevice _orderDevice; + private static CalculationMethod _calculationMethod; [ThreadStatic] private static bool _lastCompositeP; @@ -101,6 +102,11 @@ private static ThreadLocal? MersenneTesters; ComputationDevice orderDevice = EnvironmentConfiguration.OrderDevice; _mersenneDevice = mersenneDevice; _orderDevice = orderDevice; + _calculationMethod = useResidue + ? CalculationMethod.Residue + : (useLucas + ? CalculationMethod.LucasLehmer + : (useOrder ? CalculationMethod.Divisor : CalculationMethod.Incremental)); bool mersenneOnGpu = mersenneDevice == ComputationDevice.Gpu; bool orderOnGpu = EnvironmentConfiguration.UseGpuOrder; bool orderOnHybrid = EnvironmentConfiguration.UseHybridOrder; @@ -220,58 +226,20 @@ private static ThreadLocal? MersenneTesters; { MersenneTesters = new ThreadLocal(() => { - // ProcessEightBitWindows windowed pow2 ladder is the default kernel. - object tester = orderDevice switch - { - ComputationDevice.Gpu => new MersenneNumberTesterGpu( - useIncremental: !useLucas, - useOrderCache: false, - kernelType: kernelType, - useOrder: useOrder, - useGpuLucas: mersenneOnGpu, - useGpuScan: mersenneOnGpu, - useGpuOrder: orderOnGpu || orderOnHybrid, - useResidue: useResidue, - maxK: residueKMax, - orderDevice: orderDevice), - ComputationDevice.Hybrid => new MersenneNumberTesterHybrid( - useIncremental: !useLucas, - useOrderCache: false, - kernelType: kernelType, - useOrder: useOrder, - useGpuLucas: mersenneOnGpu, - useGpuScan: mersenneOnGpu, - useGpuOrder: orderOnGpu || orderOnHybrid, - useResidue: useResidue, - maxK: residueKMax, - orderDevice: orderDevice), - _ => new MersenneNumberTesterCpu( - useIncremental: !useLucas, - useOrderCache: false, - kernelType: kernelType, - useOrder: useOrder, - useGpuLucas: mersenneOnGpu, - useGpuScan: mersenneOnGpu, - useGpuOrder: orderOnGpu || orderOnHybrid, - useResidue: useResidue, - maxK: residueKMax, - orderDevice: orderDevice), - }; + object tester = CreateMersenneTester( + _calculationMethod, + orderDevice, + kernelType, + useLucas, + useOrder, + mersenneOnGpu, + orderOnGpu, + orderOnHybrid, + residueKMax); if (!useLucas) { Console.WriteLine("Warming up orders"); - if (orderDevice == ComputationDevice.Hybrid) - { - ((MersenneNumberTesterHybrid)tester).WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL); - } - else if (orderDevice == ComputationDevice.Gpu) - { - ((MersenneNumberTesterGpu)tester).WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL); - } - else - { - ((MersenneNumberTesterCpu)tester).WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL); - } + WarmUpOrders(tester, orderDevice, currentP, orderWarmupLimitOverride ?? 5_000_000UL); } return tester; @@ -575,18 +543,7 @@ private static ThreadLocal? MersenneTesters; if (!useLucas) { Console.WriteLine("Warming up orders..."); - if (orderDevice == ComputationDevice.Hybrid) - { - ((MersenneNumberTesterHybrid)MersenneTesters.Value!).WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL); - } - else if (orderDevice == ComputationDevice.Gpu) - { - ((MersenneNumberTesterGpu)MersenneTesters.Value!).WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL); - } - else - { - ((MersenneNumberTesterCpu)MersenneTesters.Value!).WarmUpOrders(currentP, orderWarmupLimitOverride ?? 5_000_000UL); - } + WarmUpOrders(MersenneTesters.Value!, orderDevice, currentP, orderWarmupLimitOverride ?? 5_000_000UL); } } @@ -696,14 +653,301 @@ private static ThreadLocal? MersenneTesters; private static bool IsMersennePrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent) { object tester = MersenneTesters!.Value!; - return _orderDevice switch + return (_calculationMethod, _orderDevice) switch { - ComputationDevice.Gpu => ((MersenneNumberTesterGpu)tester).IsMersennePrime(gpu, exponent), - ComputationDevice.Hybrid => ((MersenneNumberTesterHybrid)tester).IsMersennePrime(gpu, exponent), - _ => ((MersenneNumberTesterCpu)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.Residue, ComputationDevice.Gpu) => ((MersenneNumberTesterForResidueCalculationMethodForGpu)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.Residue, ComputationDevice.Hybrid) => ((MersenneNumberTesterForResidueCalculationMethodForHybrid)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.Residue, _) => ((MersenneNumberTesterForResidueCalculationMethodForCpu)tester).IsMersennePrime(gpu, exponent), + + (CalculationMethod.LucasLehmer, ComputationDevice.Gpu) => ((MersenneNumberTesterForLucasLehmerCalculationMethodForGpu)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.LucasLehmer, ComputationDevice.Hybrid) => ((MersenneNumberTesterForLucasLehmerCalculationMethodForHybrid)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.LucasLehmer, _) => ((MersenneNumberTesterForLucasLehmerCalculationMethodForCpu)tester).IsMersennePrime(gpu, exponent), + + (CalculationMethod.Divisor, ComputationDevice.Gpu) => ((MersenneNumberTesterForDivisorCalculationMethodForGpu)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.Divisor, ComputationDevice.Hybrid) => ((MersenneNumberTesterForDivisorCalculationMethodForHybrid)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.Divisor, _) => ((MersenneNumberTesterForDivisorCalculationMethodForCpu)tester).IsMersennePrime(gpu, exponent), + + (CalculationMethod.ByDivisor, ComputationDevice.Gpu) => ((MersenneNumberTesterForByDivisorCalculationMethodForGpu)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.ByDivisor, ComputationDevice.Hybrid) => ((MersenneNumberTesterForByDivisorCalculationMethodForHybrid)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.ByDivisor, _) => ((MersenneNumberTesterForByDivisorCalculationMethodForCpu)tester).IsMersennePrime(gpu, exponent), + + (CalculationMethod.Pow2Mod, ComputationDevice.Gpu) => ((MersenneNumberTesterForPow2ModCalculationMethodForGpu)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.Pow2Mod, ComputationDevice.Hybrid) => ((MersenneNumberTesterForPow2ModCalculationMethodForHybrid)tester).IsMersennePrime(gpu, exponent), + (CalculationMethod.Pow2Mod, _) => ((MersenneNumberTesterForPow2ModCalculationMethodForCpu)tester).IsMersennePrime(gpu, exponent), + + (_, ComputationDevice.Gpu) => ((MersenneNumberTesterForIncrementalCalculationMethodForGpu)tester).IsMersennePrime(gpu, exponent), + (_, ComputationDevice.Hybrid) => ((MersenneNumberTesterForIncrementalCalculationMethodForHybrid)tester).IsMersennePrime(gpu, exponent), + _ => ((MersenneNumberTesterForIncrementalCalculationMethodForCpu)tester).IsMersennePrime(gpu, exponent), }; } + private static object CreateMersenneTester( + CalculationMethod method, + ComputationDevice device, + GpuKernelType kernelType, + bool useLucas, + bool useOrder, + bool mersenneOnGpu, + bool orderOnGpu, + bool orderOnHybrid, + ulong residueKMax) + { + return (method, device) switch + { + (CalculationMethod.Residue, ComputationDevice.Gpu) => new MersenneNumberTesterForResidueCalculationMethodForGpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.Residue, ComputationDevice.Hybrid) => new MersenneNumberTesterForResidueCalculationMethodForHybrid( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.Residue, _) => new MersenneNumberTesterForResidueCalculationMethodForCpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + + (CalculationMethod.LucasLehmer, ComputationDevice.Gpu) => new MersenneNumberTesterForLucasLehmerCalculationMethodForGpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.LucasLehmer, ComputationDevice.Hybrid) => new MersenneNumberTesterForLucasLehmerCalculationMethodForHybrid( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.LucasLehmer, _) => new MersenneNumberTesterForLucasLehmerCalculationMethodForCpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + + (CalculationMethod.Divisor, ComputationDevice.Gpu) => new MersenneNumberTesterForDivisorCalculationMethodForGpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.Divisor, ComputationDevice.Hybrid) => new MersenneNumberTesterForDivisorCalculationMethodForHybrid( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.Divisor, _) => new MersenneNumberTesterForDivisorCalculationMethodForCpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + + (CalculationMethod.ByDivisor, ComputationDevice.Gpu) => new MersenneNumberTesterForByDivisorCalculationMethodForGpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.ByDivisor, ComputationDevice.Hybrid) => new MersenneNumberTesterForByDivisorCalculationMethodForHybrid( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.ByDivisor, _) => new MersenneNumberTesterForByDivisorCalculationMethodForCpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + + (CalculationMethod.Pow2Mod, ComputationDevice.Gpu) => new MersenneNumberTesterForPow2ModCalculationMethodForGpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.Pow2Mod, ComputationDevice.Hybrid) => new MersenneNumberTesterForPow2ModCalculationMethodForHybrid( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (CalculationMethod.Pow2Mod, _) => new MersenneNumberTesterForPow2ModCalculationMethodForCpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + + (_, ComputationDevice.Gpu) => new MersenneNumberTesterForIncrementalCalculationMethodForGpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + (_, ComputationDevice.Hybrid) => new MersenneNumberTesterForIncrementalCalculationMethodForHybrid( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + _ => new MersenneNumberTesterForIncrementalCalculationMethodForCpu( + + useOrderCache: false, + kernelType: kernelType, + useOrder: useOrder, + useGpuLucas: mersenneOnGpu, + useGpuScan: mersenneOnGpu, + useGpuOrder: orderOnGpu || orderOnHybrid, + maxK: residueKMax, + orderDevice: device), + }; + } + + private static void WarmUpOrders(object tester, ComputationDevice device, ulong exponent, ulong limit) + { + switch (_calculationMethod, device) + { + case (CalculationMethod.Residue, ComputationDevice.Gpu): + ((MersenneNumberTesterForResidueCalculationMethodForGpu)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.Residue, ComputationDevice.Hybrid): + ((MersenneNumberTesterForResidueCalculationMethodForHybrid)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.Residue, _): + ((MersenneNumberTesterForResidueCalculationMethodForCpu)tester).WarmUpOrders(exponent, limit); + break; + + case (CalculationMethod.LucasLehmer, ComputationDevice.Gpu): + ((MersenneNumberTesterForLucasLehmerCalculationMethodForGpu)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.LucasLehmer, ComputationDevice.Hybrid): + ((MersenneNumberTesterForLucasLehmerCalculationMethodForHybrid)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.LucasLehmer, _): + ((MersenneNumberTesterForLucasLehmerCalculationMethodForCpu)tester).WarmUpOrders(exponent, limit); + break; + + case (CalculationMethod.Divisor, ComputationDevice.Gpu): + ((MersenneNumberTesterForDivisorCalculationMethodForGpu)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.Divisor, ComputationDevice.Hybrid): + ((MersenneNumberTesterForDivisorCalculationMethodForHybrid)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.Divisor, _): + ((MersenneNumberTesterForDivisorCalculationMethodForCpu)tester).WarmUpOrders(exponent, limit); + break; + + case (CalculationMethod.ByDivisor, ComputationDevice.Gpu): + ((MersenneNumberTesterForByDivisorCalculationMethodForGpu)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.ByDivisor, ComputationDevice.Hybrid): + ((MersenneNumberTesterForByDivisorCalculationMethodForHybrid)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.ByDivisor, _): + ((MersenneNumberTesterForByDivisorCalculationMethodForCpu)tester).WarmUpOrders(exponent, limit); + break; + + case (CalculationMethod.Pow2Mod, ComputationDevice.Gpu): + ((MersenneNumberTesterForPow2ModCalculationMethodForGpu)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.Pow2Mod, ComputationDevice.Hybrid): + ((MersenneNumberTesterForPow2ModCalculationMethodForHybrid)tester).WarmUpOrders(exponent, limit); + break; + case (CalculationMethod.Pow2Mod, _): + ((MersenneNumberTesterForPow2ModCalculationMethodForCpu)tester).WarmUpOrders(exponent, limit); + break; + + case (_, ComputationDevice.Gpu): + ((MersenneNumberTesterForIncrementalCalculationMethodForGpu)tester).WarmUpOrders(exponent, limit); + break; + case (_, ComputationDevice.Hybrid): + ((MersenneNumberTesterForIncrementalCalculationMethodForHybrid)tester).WarmUpOrders(exponent, limit); + break; + default: + ((MersenneNumberTesterForIncrementalCalculationMethodForCpu)tester).WarmUpOrders(exponent, limit); + break; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void OnPrimeCandidateConfirmed() { diff --git a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneNumberTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneNumberTesterTests.cs index 68cbb75..0000d41 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneNumberTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneNumberTesterTests.cs @@ -13,7 +13,7 @@ public class MersenneNumberTesterTests [InlineData(136_000_005UL)] public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p) { - var tester = new MersenneNumberTesterCpu(); + var tester = new MersenneNumberTesterForIncrementalCalculationMethodForCpu(); var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { @@ -29,7 +29,7 @@ public class MersenneNumberTesterTests [InlineData(125UL)] public void WarmUpOrdersCpu_populates_cache_without_affecting_results(ulong p) { - var tester = new MersenneNumberTesterCpu(useOrderCache: true, useGpuOrder: true); + var tester = new MersenneNumberTesterForIncrementalCalculationMethodForCpu(useOrderCache: true, useGpuOrder: true); tester.WarmUpOrders(p, 1_000UL); var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try @@ -46,7 +46,7 @@ public class MersenneNumberTesterTests [InlineData(125UL)] public void WarmUpOrdersGpu_populates_cache_without_affecting_results(ulong p) { - var tester = new MersenneNumberTesterGpu(useOrderCache: true, useGpuOrder: true); + var tester = new MersenneNumberTesterForIncrementalCalculationMethodForGpu(useOrderCache: true, useGpuOrder: true); tester.WarmUpOrders(p, 1_000UL); var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try diff --git a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs index 2ff289a..60b61ca 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs @@ -10,9 +10,8 @@ public class MersenneResidueModeTests [Fact] public void IsMersennePrime_residue_mode_rejects_composite_exponent() { - var tester = new MersenneNumberTesterCpu( - useResidue: true, - useIncremental: false, + var tester = new MersenneNumberTesterForResidueCalculationMethodForCpu( + useGpuScan: false, useGpuOrder: false); var gpu = PrimeOrderCalculatorAccelerator.Rent(1); @@ -29,9 +28,8 @@ public class MersenneResidueModeTests [Fact] public void IsMersennePrime_residue_mode_accepts_prime_exponent() { - var tester = new MersenneNumberTesterCpu( - useResidue: true, - useIncremental: true, + var tester = new MersenneNumberTesterForResidueCalculationMethodForCpu( + useGpuScan: false, useGpuOrder: false, maxK: 1_000UL); diff --git a/PerfectNumbers.Core/MersenneNumberTesterTemplate.cs b/PerfectNumbers.Core/MersenneNumberTesterTemplate.cs index e424403..50e8535 100644 --- a/PerfectNumbers.Core/MersenneNumberTesterTemplate.cs +++ b/PerfectNumbers.Core/MersenneNumberTesterTemplate.cs @@ -12,341 +12,339 @@ namespace PerfectNumbers.Core; public enum GpuKernelType { - Incremental, - Pow2Mod, + Incremental, + Pow2Mod, } public enum CalculationMethod { - ByDivisor, - Divisor, - Incremental, - LucasLehmer, - Pow2Mod, - Residue, + ByDivisor, + Divisor, + Incremental, + LucasLehmer, + Pow2Mod, + Residue, } +[EnumDependentTemplate(typeof(CalculationMethod))] [DeviceDependentTemplate(typeof(ComputationDevice))] public sealed class MersenneNumberTesterTemplate( - bool useIncremental = true, - bool useOrderCache = false, - GpuKernelType kernelType = GpuKernelType.Incremental, - bool useOrder = false, - bool useGpuLucas = true, - bool useGpuScan = true, - bool useGpuOrder = true, - bool useResidue = true, - ulong maxK = 5_000_000UL, - ComputationDevice orderDevice = ComputationDevice.Gpu) + bool useOrderCache = false, + GpuKernelType kernelType = GpuKernelType.Incremental, + bool useOrder = false, + bool useGpuLucas = true, + bool useGpuScan = true, + bool useGpuOrder = true, + ulong maxK = 5_000_000UL, + ComputationDevice orderDevice = ComputationDevice.Gpu) { - private readonly bool _useResidue = useResidue; - private readonly bool _useIncremental = useIncremental && !useResidue; - private readonly ulong _maxK = maxK; - private readonly Func _sharesFactorWithExponentMinusOne = orderDevice switch - { - ComputationDevice.Hybrid => static (gpu, exponent) => exponent.SharesFactorWithExponentMinusOneHybrid(gpu), - _ when useGpuScan => static (gpu, exponent) => exponent.SharesFactorWithExponentMinusOneGpu(gpu), - _ => static (_, exponent) => exponent.SharesFactorWithExponentMinusOneCpu(), - }; - - private static readonly ConcurrentDictionary _orderCache = []; - // TODO: Swap this ConcurrentDictionary for the pooled dictionary variant highlighted in - // Pow2MontgomeryModBenchmarks once order warmups reuse deterministic divisor-cycle snapshots; the pooled approach removed - // the locking overhead when scanning p >= 138M in those benchmarks. - private readonly MersenneNumberIncrementalGpuTester _incrementalGpuTester = new(kernelType, useGpuOrder); - private readonly MersenneNumberIncrementalCpuTester _incrementalCpuTester = new(kernelType); - private readonly MersenneNumberOrderGpuTester _orderGpuTester = new(kernelType, useGpuOrder); - private readonly MersenneNumberOrderCpuTester _orderCpuTester = new(kernelType); - private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerGpuTester = new(); - private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerCpuTester = new(); - private readonly MersenneNumberResidueGpuTester? _residueGpuTester = useResidue ? new(useGpuOrder) : null; - private readonly MersenneNumberResidueCpuTester? _residueCpuTester = useResidue ? new() : null; - - // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags. - // Also consider injecting a shared cycles cache (MersenneDivisorCycles.Shared) to both CPU and GPU testers. - - private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; - - public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL) - { - if (!useOrderCache) - { - return; - } - - UInt128 twoP = (UInt128)exponent << 1; // 2 * p - bool lastIsSeven = (exponent & 3UL) == 3UL; + private static readonly ConcurrentDictionary _orderCache = []; + // TODO: Swap this ConcurrentDictionary for the pooled dictionary variant highlighted in + // Pow2MontgomeryModBenchmarks once order warmups reuse deterministic divisor-cycle snapshots; the pooled approach removed + // the locking overhead when scanning p >= 138M in those benchmarks. +#if CalculationMethod_Residue #if DEVICE_GPU || DEVICE_HYBRID - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); -#endif - if (!useGpuOrder) - { - UInt128 k = UInt128.One; - for (; k <= limit; k++) - { - UInt128 q = twoP * k + UInt128.One; - ulong remainder = q.Mod10(); - bool shouldCheck = lastIsSeven - ? remainder == 7UL || remainder == 9UL || remainder == 3UL - : remainder == 1UL || remainder == 3UL || remainder == 9UL; - - if (shouldCheck) - { - ulong mod8 = q.Mod8(); - if (mod8 != 1UL && mod8 != 7UL) - { - continue; - } - - if (q.Mod3() == 0UL || q.Mod5() == 0UL) - { - continue; - } - } - - if (!shouldCheck || _orderCache.ContainsKey(q)) - { - continue; - } - - _orderCache[q] = -#if DEVICE_GPU - q.CalculateOrderGpu(gpu); -#elif DEVICE_HYBRID - q.CalculateOrderHybrid(gpu); + private readonly MersenneNumberResidueGpuTester _residueTester = new(useGpuOrder); #else - q.CalculateOrderCpu(); + private readonly MersenneNumberResidueCpuTester _residueTester = new(); #endif - } + private readonly ulong _maxK = maxK; +#elif CalculationMethod_LucasLehmer #if DEVICE_GPU || DEVICE_HYBRID - PrimeOrderCalculatorAccelerator.Return(gpu); -#endif - return; - } - - ArrayPool uInt128Pool = ThreadStaticPools.UInt128Pool; - UInt128[] qsBuffer = uInt128Pool.Rent((int)limit); - int idx = 0; - UInt128 k2 = 1UL; - for (; k2 <= limit; k2++) - { - UInt128 q2 = twoP * k2 + 1UL; - ulong remainder2 = q2.Mod10(); - bool shouldCheck2 = lastIsSeven - ? remainder2 == 7UL || remainder2 == 9UL || remainder2 == 3UL - : remainder2 == 1UL || remainder2 == 3UL || remainder2 == 9UL; - - if (shouldCheck2) - { - ulong mod8 = q2.Mod8(); - if (mod8 != 1UL && mod8 != 7UL) - { - shouldCheck2 = false; - } - else if (q2.Mod3() == 0UL || q2.Mod5() == 0UL) - { - shouldCheck2 = false; - } - } - - if (!shouldCheck2 || _orderCache.ContainsKey(q2)) - { - continue; - } - - qsBuffer[idx++] = q2; - } - - if (idx == 0) - { -#if DEVICE_GPU || DEVICE_HYBRID - PrimeOrderCalculatorAccelerator.Return(gpu); -#endif - ThreadStaticPools.UInt128Pool.Return(qsBuffer); - return; - } - - UInt128[] qs = qsBuffer; - - var acceleratorIndex = AcceleratorPool.Shared.Rent(); - var accelerator = _accelerators[acceleratorIndex]; - var stream = AcceleratorStreamPool.Rent(acceleratorIndex); - var orderKernel = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.OrderKernelScan).Order!; - - int batchSize = GpuConstants.ScanBatchSize; - ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; - int offset = 0; - var gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool; - MemoryBuffer1D? qBuffer; - MemoryBuffer1D? orderBuffer; - - while (offset < idx) - { - int count = Math.Min(batchSize, idx - offset); - qBuffer = accelerator.Allocate1D(count); - orderBuffer = accelerator.Allocate1D(count); - - var tmp = gpuUInt128Pool.Rent(count); - for (int i = 0; i < count; i++) - { - tmp[i] = (GpuUInt128)qs[offset + i]; - } - - qBuffer.View.CopyFromCPU(stream, ref tmp[0], count); - gpuUInt128Pool.Return(tmp); - orderKernel(stream, count, exponent, divMul, qBuffer.View, orderBuffer.View); - stream.Synchronize(); - - ulong[] orders = orderBuffer.GetAsArray1D(); - for (int i = 0; i < count; i++) - { - ulong order = orders[i]; - if (order == 0UL) - { - order = -#if DEVICE_GPU - qs[offset + i].CalculateOrderGpu(gpu); -#elif DEVICE_HYBRID - qs[offset + i].CalculateOrderHybrid(gpu); + private readonly MersenneNumberIncrementalGpuTester _incrementalTester = new(kernelType, useGpuOrder); + private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerTester = new(); #else - qs[offset + i].CalculateOrderCpu(); + private readonly MersenneNumberIncrementalCpuTester _incrementalTester = new(kernelType); + private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerTester = new(); #endif - } - - if (useOrderCache) - { - _orderCache[qs[offset + i]] = order; - } - } - - offset += count; - orderBuffer.Dispose(); - qBuffer.Dispose(); - } - - AcceleratorStreamPool.Return(acceleratorIndex,stream); +#elif CalculationMethod_Divisor || CalculationMethod_ByDivisor #if DEVICE_GPU || DEVICE_HYBRID - PrimeOrderCalculatorAccelerator.Return(gpu); + private readonly MersenneNumberOrderGpuTester _orderTester = new(kernelType, useGpuOrder); +#else + private readonly MersenneNumberOrderCpuTester _orderTester = new(kernelType); +#endif +#else +#if DEVICE_GPU || DEVICE_HYBRID + private readonly MersenneNumberIncrementalGpuTester _incrementalTester = new(kernelType, useGpuOrder); +#else + private readonly MersenneNumberIncrementalCpuTester _incrementalTester = new(kernelType); +#endif #endif - uInt128Pool.Return(qs); - } - public bool IsMersennePrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent) - { - // Safe early rejections using known orders for tiny primes: - // 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime. - if ((exponent % 3UL) == 0UL && exponent != 3UL) - { - // TODO: Replace this `% 3` check with ULongExtensions.Mod3 to align the early rejection with - // the benchmarked bitmask helper instead of generic modulo for CPU workloads. - return false; - } + private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; - if ((exponent & 3UL) == 1UL && _sharesFactorWithExponentMinusOne(gpu, exponent)) - { - return false; - } + public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL) + { + if (!useOrderCache) + { + return; + } - // Skip even exponents: for even p > 2, M_p is composite; here p is large. - if ((exponent & 1UL) == 0UL) - { - return false; - } + UInt128 k, q; + UInt128 twoP = (UInt128)exponent << 1; // 2 * p + bool lastIsSeven = (exponent & 3UL) == 3UL; +#if DEVICE_GPU || DEVICE_HYBRID + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); +#endif + if (!useGpuOrder) + { + k = UInt128.One; + for (; k <= limit; k++) + { + q = twoP * k + UInt128.One; + ulong remainder = q.Mod10(); + bool shouldCheck = lastIsSeven + ? remainder == 7UL || remainder == 9UL || remainder == 3UL + : remainder == 1UL || remainder == 3UL || remainder == 9UL; - bool prePrime = true; - UInt128 twoP = (UInt128)exponent << 1; // 2 * p - // UInt128 maxDivisor = new(ulong.MaxValue, ulong.MaxValue); - UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1; - LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; + if (shouldCheck) + { + ulong mod8 = q.Mod8(); + if (mod8 != 1UL && mod8 != 7UL) + { + continue; + } - // In residue mode the residue scan is the final primality check. - if (_useResidue) - { - if (_maxK < maxK) - { - maxK = _maxK; - } + if (q.Mod3() == 0UL || q.Mod5() == 0UL) + { + continue; + } + } - if (useGpuScan) - { - _residueGpuTester!.Scan(gpu, exponent, twoP, lastDigit, maxK, ref prePrime); - } - else - { - _residueCpuTester!.Scan(exponent, twoP, lastDigit, maxK, ref prePrime); - } + if (!shouldCheck || _orderCache.ContainsKey(q)) + { + continue; + } - return prePrime; - } + _orderCache[q] = +#if DEVICE_GPU + q.CalculateOrderGpu(gpu); +#elif DEVICE_HYBRID + q.CalculateOrderHybrid(gpu); +#else + q.CalculateOrderCpu(); +#endif + } +#if DEVICE_GPU || DEVICE_HYBRID + PrimeOrderCalculatorAccelerator.Return(gpu); +#endif + return; + } - if (!_useIncremental) - { - // Optional prefilter before Lucas–Lehmer using the same GPU/CPU scan path. - // Scan a limited range of k to quickly reject obvious cases. - UInt128 twoPPre = (UInt128)exponent << 1; - UInt128 maxKPre = (UInt128)PreLucasPreScanK; - LastDigit lastDigitPre = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; - if (useGpuScan) - { - _incrementalGpuTester.Scan(exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); - } - else - { - _incrementalCpuTester.Scan(gpu, exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); - } + ArrayPool uInt128Pool = ThreadStaticPools.UInt128Pool; + UInt128[] qsBuffer = uInt128Pool.Rent((int)limit); + int idx = 0; + k = UInt128.One; + for (; k <= limit; k++) + { + q = twoP * k + UInt128.One; + ulong remainder2 = q.Mod10(); + bool shouldCheck2 = lastIsSeven + ? remainder2 == 7UL || remainder2 == 9UL || remainder2 == 3UL + : remainder2 == 1UL || remainder2 == 3UL || remainder2 == 9UL; - if (!prePrime) - { - return false; - } + if (shouldCheck2) + { + ulong mod8 = q.Mod8(); + if (mod8 != 1UL && mod8 != 7UL) + { + shouldCheck2 = false; + } + else if (q.Mod3() == 0UL || q.Mod5() == 0UL) + { + shouldCheck2 = false; + } + } - return useGpuLucas - ? _lucasLehmerGpuTester.IsPrime(gpu, exponent, runOnGpu: true) - : _lucasLehmerCpuTester.IsPrime(gpu, exponent); - } + if (!shouldCheck2 || _orderCache.ContainsKey(q)) + { + continue; + } - bool isPrime = true; + qsBuffer[idx++] = q; + } - if (useOrder) - { - if (useGpuOrder) - { - _orderGpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); - } - else - { - _orderCpuTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref isPrime); - } - } - else - { - if (useGpuScan) - { - _incrementalGpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); - } - else - { - _incrementalCpuTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref isPrime); - } - } + if (idx == 0) + { +#if DEVICE_GPU || DEVICE_HYBRID + PrimeOrderCalculatorAccelerator.Return(gpu); +#endif + ThreadStaticPools.UInt128Pool.Return(qsBuffer); + return; + } - return isPrime; - } + UInt128[] qs = qsBuffer; - // Limit of k to scan in the pre-Lucas prescan - public const int PreLucasPreScanK = 5_000_000; + var acceleratorIndex = AcceleratorPool.Shared.Rent(); + var accelerator = _accelerators[acceleratorIndex]; + var stream = AcceleratorStreamPool.Rent(acceleratorIndex); + var orderKernel = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.OrderKernelScan).Order!; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool DividesMersenne(ulong exponent, ulong prime) - { - return 2UL.ModPow64(exponent, prime) == 1UL; - } + int batchSize = GpuConstants.ScanBatchSize; + ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; + int offset = 0; + var gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool; + MemoryBuffer1D? qBuffer; + MemoryBuffer1D? orderBuffer; + + while (offset < idx) + { + int count = Math.Min(batchSize, idx - offset); + qBuffer = accelerator.Allocate1D(count); + orderBuffer = accelerator.Allocate1D(count); + + var tmp = gpuUInt128Pool.Rent(count); + for (int i = 0; i < count; i++) + { + tmp[i] = (GpuUInt128)qs[offset + i]; + } + + qBuffer.View.CopyFromCPU(stream, ref tmp[0], count); + gpuUInt128Pool.Return(tmp); + orderKernel(stream, count, exponent, divMul, qBuffer.View, orderBuffer.View); + stream.Synchronize(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView qs, ArrayView orders) - { - UInt128 q = qs[index]; - UInt128 pow = exponent.PowMod(q); - orders[index] = pow == 1UL ? exponent : 0UL; - } -} + ulong[] orders = orderBuffer.GetAsArray1D(); + for (int i = 0; i < count; i++) + { + ulong order = orders[i]; + if (order == 0UL) + { + order = +#if DEVICE_GPU + qs[offset + i].CalculateOrderGpu(gpu); +#elif DEVICE_HYBRID + qs[offset + i].CalculateOrderHybrid(gpu); +#else + qs[offset + i].CalculateOrderCpu(); +#endif + } + + if (useOrderCache) + { + _orderCache[qs[offset + i]] = order; + } + } + + offset += count; + orderBuffer.Dispose(); + qBuffer.Dispose(); + } + + AcceleratorStreamPool.Return(acceleratorIndex,stream); +#if DEVICE_GPU || DEVICE_HYBRID + PrimeOrderCalculatorAccelerator.Return(gpu); +#endif + uInt128Pool.Return(qs); + } + + public bool IsMersennePrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent) + { + // Safe early rejections using known orders for tiny primes: + // 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime. + if ((exponent % 3UL) == 0UL && exponent != 3UL) + { + // TODO: Replace this `% 3` check with ULongExtensions.Mod3 to align the early rejection with + // the benchmarked bitmask helper instead of generic modulo for CPU workloads. + return false; + } + +#if DEVICE_HYBRID + if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOneHybrid(gpu)) + { + return false; + } +#elif DEVICE_GPU + if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOneGpu(gpu)) + { + return false; + } +#else + if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOneCpu()) + { + return false; + } +#endif + + // Skip even exponents: for even p > 2, M_p is composite; here p is large. + if ((exponent & 1UL) == 0UL) + { + return false; + } + + UInt128 twoP = (UInt128)exponent << 1; // 2 * p + UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1; + LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; + +#if CalculationMethod_Residue + bool prePrime = true; + if (_maxK < maxK) + { + maxK = _maxK; + } + +#if DEVICE_GPU || DEVICE_HYBRID + _residueTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref prePrime); +#else + _residueTester.Scan(exponent, twoP, lastDigit, maxK, ref prePrime); +#endif + + return prePrime; +#elif CalculationMethod_LucasLehmer + bool prePrime = true; + UInt128 twoPPre = (UInt128)exponent << 1; + UInt128 maxKPre = (UInt128)PreLucasPreScanK; + LastDigit lastDigitPre = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; +#if DEVICE_GPU || DEVICE_HYBRID + _incrementalTester.Scan(exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); + if (!prePrime) + { + return false; + } + + return _lucasLehmerTester.IsPrime(gpu, exponent, runOnGpu: true); +#else + _incrementalTester.Scan(gpu, exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); + if (!prePrime) + { + return false; + } + + return _lucasLehmerTester.IsPrime(gpu, exponent); +#endif +#elif CalculationMethod_Divisor || CalculationMethod_ByDivisor + bool isPrime = true; +#if DEVICE_GPU || DEVICE_HYBRID + _orderTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); +#else + _orderTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref isPrime); +#endif + return isPrime; +#else + bool isPrime = true; +#if DEVICE_GPU || DEVICE_HYBRID + _incrementalTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); +#else + _incrementalTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref isPrime); +#endif + return isPrime; +#endif + } + + // Limit of k to scan in the pre-Lucas prescan + public const int PreLucasPreScanK = 5_000_000; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool DividesMersenne(ulong exponent, ulong prime) + { + return 2UL.ModPow64(exponent, prime) == 1UL; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView qs, ArrayView orders) + { + UInt128 q = qs[index]; + UInt128 pow = exponent.PowMod(q); + orders[index] = pow == 1UL ? exponent : 0UL; + } +} \ No newline at end of file