MersenneNumberTesterTemplate made dependent on the calculation method to eliminate unnecessary branching.

This commit is contained in:
mlemanczyk committed 2025-12-19 20:01:45 +01:00
1 parent c9a421a1b4
commit c5c42b0c19
5 files changed
+630 -395

No files matched your search

+12 -17
View File
@@ -227,7 +227,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterCpu(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true)); mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForCpu( maxK: 1_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => 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 mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterCpu( mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForCpu(
useIncremental: true,
useResidue: true,
maxK: 1_024UL), trackAllValues: true)); maxK: 1_024UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => 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 mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterCpu( mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForCpu(
useIncremental: true,
useResidue: true,
maxK: 5_000_000UL), trackAllValues: true)); maxK: 5_000_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => 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 mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterCpu( mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForCpu(
useIncremental: true,
useResidue: true,
maxK: 5_000_000UL), trackAllValues: true)); maxK: 5_000_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => 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 mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterCpu( mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForCpu(
useIncremental: true,
useGpuScan: false, useGpuScan: false,
useGpuOrder: false, useGpuOrder: false,
useResidue: true,
maxK: 1_024UL), trackAllValues: true)); maxK: 1_024UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => 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 mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterCpu( mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForResidueCalculationMethodForCpu(
useIncremental: false,
kernelType: configuration.KernelType, kernelType: configuration.KernelType,
useGpuScan: true, useGpuScan: true,
useGpuOrder: configuration.UseGpuOrder, useGpuOrder: configuration.UseGpuOrder,
useResidue: true,
maxK: configuration.ResidueMaxK), trackAllValues: true)); maxK: configuration.ResidueMaxK), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => 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 primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!; var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!;
mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterCpu(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true)); mersenneField.SetValue(null, new ThreadLocal<object>(() => new MersenneNumberTesterForIncrementalCalculationMethodForCpu( maxK: 1_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
+309 -65
View File
@@ -29,6 +29,7 @@ private static ThreadLocal<object>? MersenneTesters;
private static int _testProcessedPrimeCount; private static int _testProcessedPrimeCount;
private static ComputationDevice _mersenneDevice; private static ComputationDevice _mersenneDevice;
private static ComputationDevice _orderDevice; private static ComputationDevice _orderDevice;
private static CalculationMethod _calculationMethod;
[ThreadStatic] [ThreadStatic]
private static bool _lastCompositeP; private static bool _lastCompositeP;
@@ -101,6 +102,11 @@ private static ThreadLocal<object>? MersenneTesters;
ComputationDevice orderDevice = EnvironmentConfiguration.OrderDevice; ComputationDevice orderDevice = EnvironmentConfiguration.OrderDevice;
_mersenneDevice = mersenneDevice; _mersenneDevice = mersenneDevice;
_orderDevice = orderDevice; _orderDevice = orderDevice;
_calculationMethod = useResidue
? CalculationMethod.Residue
: (useLucas
? CalculationMethod.LucasLehmer
: (useOrder ? CalculationMethod.Divisor : CalculationMethod.Incremental));
bool mersenneOnGpu = mersenneDevice == ComputationDevice.Gpu; bool mersenneOnGpu = mersenneDevice == ComputationDevice.Gpu;
bool orderOnGpu = EnvironmentConfiguration.UseGpuOrder; bool orderOnGpu = EnvironmentConfiguration.UseGpuOrder;
bool orderOnHybrid = EnvironmentConfiguration.UseHybridOrder; bool orderOnHybrid = EnvironmentConfiguration.UseHybridOrder;
@@ -220,58 +226,20 @@ private static ThreadLocal<object>? MersenneTesters;
{ {
MersenneTesters = new ThreadLocal<object>(() => MersenneTesters = new ThreadLocal<object>(() =>
{ {
// ProcessEightBitWindows windowed pow2 ladder is the default kernel. object tester = CreateMersenneTester(
object tester = orderDevice switch _calculationMethod,
{ orderDevice,
ComputationDevice.Gpu => new MersenneNumberTesterGpu( kernelType,
useIncremental: !useLucas, useLucas,
useOrderCache: false, useOrder,
kernelType: kernelType, mersenneOnGpu,
useOrder: useOrder, orderOnGpu,
useGpuLucas: mersenneOnGpu, orderOnHybrid,
useGpuScan: mersenneOnGpu, residueKMax);
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),
};
if (!useLucas) if (!useLucas)
{ {
Console.WriteLine("Warming up orders"); Console.WriteLine("Warming up orders");
if (orderDevice == ComputationDevice.Hybrid) WarmUpOrders(tester, orderDevice, currentP, orderWarmupLimitOverride ?? 5_000_000UL);
{
((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);
}
} }
return tester; return tester;
@@ -575,18 +543,7 @@ private static ThreadLocal<object>? MersenneTesters;
if (!useLucas) if (!useLucas)
{ {
Console.WriteLine("Warming up orders..."); Console.WriteLine("Warming up orders...");
if (orderDevice == ComputationDevice.Hybrid) WarmUpOrders(MersenneTesters.Value!, orderDevice, currentP, orderWarmupLimitOverride ?? 5_000_000UL);
{
((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);
}
} }
} }
@@ -696,14 +653,301 @@ private static ThreadLocal<object>? MersenneTesters;
private static bool IsMersennePrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent) private static bool IsMersennePrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent)
{ {
object tester = MersenneTesters!.Value!; object tester = MersenneTesters!.Value!;
return _orderDevice switch return (_calculationMethod, _orderDevice) switch
{ {
ComputationDevice.Gpu => ((MersenneNumberTesterGpu)tester).IsMersennePrime(gpu, exponent), (CalculationMethod.Residue, ComputationDevice.Gpu) => ((MersenneNumberTesterForResidueCalculationMethodForGpu)tester).IsMersennePrime(gpu, exponent),
ComputationDevice.Hybrid => ((MersenneNumberTesterHybrid)tester).IsMersennePrime(gpu, exponent), (CalculationMethod.Residue, ComputationDevice.Hybrid) => ((MersenneNumberTesterForResidueCalculationMethodForHybrid)tester).IsMersennePrime(gpu, exponent),
_ => ((MersenneNumberTesterCpu)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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void OnPrimeCandidateConfirmed() private static void OnPrimeCandidateConfirmed()
{ {
@@ -13,7 +13,7 @@ public class MersenneNumberTesterTests
[InlineData(136_000_005UL)] [InlineData(136_000_005UL)]
public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p) 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); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try try
{ {
@@ -29,7 +29,7 @@ public class MersenneNumberTesterTests
[InlineData(125UL)] [InlineData(125UL)]
public void WarmUpOrdersCpu_populates_cache_without_affecting_results(ulong p) 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); tester.WarmUpOrders(p, 1_000UL);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try try
@@ -46,7 +46,7 @@ public class MersenneNumberTesterTests
[InlineData(125UL)] [InlineData(125UL)]
public void WarmUpOrdersGpu_populates_cache_without_affecting_results(ulong p) 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); tester.WarmUpOrders(p, 1_000UL);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
try try
@@ -10,9 +10,8 @@ public class MersenneResidueModeTests
[Fact] [Fact]
public void IsMersennePrime_residue_mode_rejects_composite_exponent() public void IsMersennePrime_residue_mode_rejects_composite_exponent()
{ {
var tester = new MersenneNumberTesterCpu( var tester = new MersenneNumberTesterForResidueCalculationMethodForCpu(
useResidue: true,
useIncremental: false,
useGpuScan: false, useGpuScan: false,
useGpuOrder: false); useGpuOrder: false);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
@@ -29,9 +28,8 @@ public class MersenneResidueModeTests
[Fact] [Fact]
public void IsMersennePrime_residue_mode_accepts_prime_exponent() public void IsMersennePrime_residue_mode_accepts_prime_exponent()
{ {
var tester = new MersenneNumberTesterCpu( var tester = new MersenneNumberTesterForResidueCalculationMethodForCpu(
useResidue: true,
useIncremental: true,
useGpuScan: false, useGpuScan: false,
useGpuOrder: false, useGpuOrder: false,
maxK: 1_000UL); maxK: 1_000UL);
@@ -12,341 +12,339 @@ namespace PerfectNumbers.Core;
public enum GpuKernelType public enum GpuKernelType
{ {
Incremental, Incremental,
Pow2Mod, Pow2Mod,
} }
public enum CalculationMethod public enum CalculationMethod
{ {
ByDivisor, ByDivisor,
Divisor, Divisor,
Incremental, Incremental,
LucasLehmer, LucasLehmer,
Pow2Mod, Pow2Mod,
Residue, Residue,
} }
[EnumDependentTemplate(typeof(CalculationMethod))]
[DeviceDependentTemplate(typeof(ComputationDevice))] [DeviceDependentTemplate(typeof(ComputationDevice))]
public sealed class MersenneNumberTesterTemplate( public sealed class MersenneNumberTesterTemplate(
bool useIncremental = true, bool useOrderCache = false,
bool useOrderCache = false, GpuKernelType kernelType = GpuKernelType.Incremental,
GpuKernelType kernelType = GpuKernelType.Incremental, bool useOrder = false,
bool useOrder = false, bool useGpuLucas = true,
bool useGpuLucas = true, bool useGpuScan = true,
bool useGpuScan = true, bool useGpuOrder = true,
bool useGpuOrder = true, ulong maxK = 5_000_000UL,
bool useResidue = true, ComputationDevice orderDevice = ComputationDevice.Gpu)
ulong maxK = 5_000_000UL,
ComputationDevice orderDevice = ComputationDevice.Gpu)
{ {
private readonly bool _useResidue = useResidue; private static readonly ConcurrentDictionary<UInt128, ulong> _orderCache = [];
private readonly bool _useIncremental = useIncremental && !useResidue; // TODO: Swap this ConcurrentDictionary for the pooled dictionary variant highlighted in
private readonly ulong _maxK = maxK; // Pow2MontgomeryModBenchmarks once order warmups reuse deterministic divisor-cycle snapshots; the pooled approach removed
private readonly Func<PrimeOrderCalculatorAccelerator, ulong, bool> _sharesFactorWithExponentMinusOne = orderDevice switch // the locking overhead when scanning p >= 138M in those benchmarks.
{ #if CalculationMethod_Residue
ComputationDevice.Hybrid => static (gpu, exponent) => exponent.SharesFactorWithExponentMinusOneHybrid(gpu),
_ when useGpuScan => static (gpu, exponent) => exponent.SharesFactorWithExponentMinusOneGpu(gpu),
_ => static (_, exponent) => exponent.SharesFactorWithExponentMinusOneCpu(),
};
private static readonly ConcurrentDictionary<UInt128, ulong> _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;
#if DEVICE_GPU || DEVICE_HYBRID #if DEVICE_GPU || DEVICE_HYBRID
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); private readonly MersenneNumberResidueGpuTester _residueTester = new(useGpuOrder);
#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);
#else #else
q.CalculateOrderCpu(); private readonly MersenneNumberResidueCpuTester _residueTester = new();
#endif #endif
} private readonly ulong _maxK = maxK;
#elif CalculationMethod_LucasLehmer
#if DEVICE_GPU || DEVICE_HYBRID #if DEVICE_GPU || DEVICE_HYBRID
PrimeOrderCalculatorAccelerator.Return(gpu); private readonly MersenneNumberIncrementalGpuTester _incrementalTester = new(kernelType, useGpuOrder);
#endif private readonly MersenneNumberLucasLehmerGpuTester _lucasLehmerTester = new();
return;
}
ArrayPool<UInt128> 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<GpuUInt128, Stride1D.Dense>? qBuffer;
MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer;
while (offset < idx)
{
int count = Math.Min(batchSize, idx - offset);
qBuffer = accelerator.Allocate1D<GpuUInt128>(count);
orderBuffer = accelerator.Allocate1D<ulong>(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);
#else #else
qs[offset + i].CalculateOrderCpu(); private readonly MersenneNumberIncrementalCpuTester _incrementalTester = new(kernelType);
private readonly MersenneNumberLucasLehmerCpuTester _lucasLehmerTester = new();
#endif #endif
} #elif CalculationMethod_Divisor || CalculationMethod_ByDivisor
if (useOrderCache)
{
_orderCache[qs[offset + i]] = order;
}
}
offset += count;
orderBuffer.Dispose();
qBuffer.Dispose();
}
AcceleratorStreamPool.Return(acceleratorIndex,stream);
#if DEVICE_GPU || DEVICE_HYBRID #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 #endif
uInt128Pool.Return(qs);
}
public bool IsMersennePrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent) private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
{
// 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 ((exponent & 3UL) == 1UL && _sharesFactorWithExponentMinusOne(gpu, exponent)) public void WarmUpOrders(ulong exponent, ulong limit = 5_000_000UL)
{ {
return false; if (!useOrderCache)
} {
return;
}
// Skip even exponents: for even p > 2, M_p is composite; here p is large. UInt128 k, q;
if ((exponent & 1UL) == 0UL) UInt128 twoP = (UInt128)exponent << 1; // 2 * p
{ bool lastIsSeven = (exponent & 3UL) == 3UL;
return false; #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; if (shouldCheck)
UInt128 twoP = (UInt128)exponent << 1; // 2 * p {
// UInt128 maxDivisor = new(ulong.MaxValue, ulong.MaxValue); ulong mod8 = q.Mod8();
UInt128 maxK = UInt128Numbers.Two.Pow(exponent) / 2 + 1; if (mod8 != 1UL && mod8 != 7UL)
LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; {
continue;
}
// In residue mode the residue scan is the final primality check. if (q.Mod3() == 0UL || q.Mod5() == 0UL)
if (_useResidue) {
{ continue;
if (_maxK < maxK) }
{ }
maxK = _maxK;
}
if (useGpuScan) if (!shouldCheck || _orderCache.ContainsKey(q))
{ {
_residueGpuTester!.Scan(gpu, exponent, twoP, lastDigit, maxK, ref prePrime); continue;
} }
else
{
_residueCpuTester!.Scan(exponent, twoP, lastDigit, maxK, ref prePrime);
}
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) ArrayPool<UInt128> uInt128Pool = ThreadStaticPools.UInt128Pool;
{ UInt128[] qsBuffer = uInt128Pool.Rent((int)limit);
// Optional prefilter before Lucas–Lehmer using the same GPU/CPU scan path. int idx = 0;
// Scan a limited range of k to quickly reject obvious cases. k = UInt128.One;
UInt128 twoPPre = (UInt128)exponent << 1; for (; k <= limit; k++)
UInt128 maxKPre = (UInt128)PreLucasPreScanK; {
LastDigit lastDigitPre = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; q = twoP * k + UInt128.One;
if (useGpuScan) ulong remainder2 = q.Mod10();
{ bool shouldCheck2 = lastIsSeven
_incrementalGpuTester.Scan(exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); ? remainder2 == 7UL || remainder2 == 9UL || remainder2 == 3UL
} : remainder2 == 1UL || remainder2 == 3UL || remainder2 == 9UL;
else
{
_incrementalCpuTester.Scan(gpu, exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime);
}
if (!prePrime) if (shouldCheck2)
{ {
return false; ulong mod8 = q.Mod8();
} if (mod8 != 1UL && mod8 != 7UL)
{
shouldCheck2 = false;
}
else if (q.Mod3() == 0UL || q.Mod5() == 0UL)
{
shouldCheck2 = false;
}
}
return useGpuLucas if (!shouldCheck2 || _orderCache.ContainsKey(q))
? _lucasLehmerGpuTester.IsPrime(gpu, exponent, runOnGpu: true) {
: _lucasLehmerCpuTester.IsPrime(gpu, exponent); continue;
} }
bool isPrime = true; qsBuffer[idx++] = q;
}
if (useOrder) if (idx == 0)
{ {
if (useGpuOrder) #if DEVICE_GPU || DEVICE_HYBRID
{ PrimeOrderCalculatorAccelerator.Return(gpu);
_orderGpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); #endif
} ThreadStaticPools.UInt128Pool.Return(qsBuffer);
else return;
{ }
_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);
}
}
return isPrime; UInt128[] qs = qsBuffer;
}
// Limit of k to scan in the pre-Lucas prescan var acceleratorIndex = AcceleratorPool.Shared.Rent();
public const int PreLucasPreScanK = 5_000_000; var accelerator = _accelerators[acceleratorIndex];
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
var orderKernel = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.OrderKernelScan).Order!;
[MethodImpl(MethodImplOptions.AggressiveInlining)] int batchSize = GpuConstants.ScanBatchSize;
private static bool DividesMersenne(ulong exponent, ulong prime) ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL;
{ int offset = 0;
return 2UL.ModPow64(exponent, prime) == 1UL; var gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
} MemoryBuffer1D<GpuUInt128, Stride1D.Dense>? qBuffer;
MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer;
[MethodImpl(MethodImplOptions.AggressiveInlining)] while (offset < idx)
private static void LegacyOrderKernel(Index1D index, ulong exponent, ArrayView<UInt128> qs, ArrayView<ulong> orders) {
{ int count = Math.Min(batchSize, idx - offset);
UInt128 q = qs[index]; qBuffer = accelerator.Allocate1D<GpuUInt128>(count);
UInt128 pow = exponent.PowMod(q); orderBuffer = accelerator.Allocate1D<ulong>(count);
orders[index] = pow == 1UL ? exponent : 0UL;
} 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);
#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<UInt128> qs, ArrayView<ulong> orders)
{
UInt128 q = qs[index];
UInt128 pow = exponent.PowMod(q);
orders[index] = pow == 1UL ? exponent : 0UL;
}
} }