Refine heuristic prime tester integration
This commit is contained in:
1 parent
21d645fcf0
commit
1e5eadac34
10 files changed
+186
-263
No files matched your search
@@ -32,8 +32,8 @@ public class PrimeTesterBenchmarks
|
||||
}
|
||||
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
_cpuTester.IsPrime(_candidates[0], cancellationToken);
|
||||
_gpuTester.IsPrimeGpu(_candidates[0], cancellationToken);
|
||||
_cpuTester.IsPrimeCpu(_candidates[0], cancellationToken);
|
||||
_gpuTester.IsPrimeGpu(_candidates[0]);
|
||||
_ = Prime.Numbers.IsPrime(_candidates[0]);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class PrimeTesterBenchmarks
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (tester.IsPrime(values[i], cancellationToken))
|
||||
if (tester.IsPrimeCpu(values[i], cancellationToken))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ public class PrimeTesterBenchmarks
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (PrimeTester.IsPrimeInternal(values[i], cancellationToken))
|
||||
if (PrimeTester.IsPrimeCp(values[i], cancellationToken))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ public sealed class HeuristicPrimeTester
|
||||
public readonly bool RequiresCycleComputation = !hasCycleLengthHint;
|
||||
}
|
||||
|
||||
public static bool EnableHeuristicPrimeTesting { get; set; } = true;
|
||||
|
||||
public static int HeuristicGpuDivisorBatchSize { get; set; } = 4_096;
|
||||
|
||||
@@ -82,30 +81,25 @@ public sealed class HeuristicPrimeTester
|
||||
|
||||
public static HeuristicPrimeTester Exclusive => _tester ??= new();
|
||||
|
||||
public bool IsPrime(ulong n, CancellationToken ct)
|
||||
public bool IsPrimeCpu(ulong n, CancellationToken ct)
|
||||
{
|
||||
if (!EnableHeuristicPrimeTesting)
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
byte nMod10 = (byte)n.Mod10();
|
||||
ulong sqrtLimit = ComputeHeuristicSqrt(n);
|
||||
|
||||
if (sqrtLimit < 3UL)
|
||||
{
|
||||
return PrimeTester.IsPrimeInternal(n, ct);
|
||||
return EvaluateWithOpenNumericFallback(n);
|
||||
}
|
||||
|
||||
return HeuristicIsPrimeCpu(n);
|
||||
bool includeGroupB = UseHeuristicGroupBTrialDivision;
|
||||
return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10, includeGroupB);
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong n)
|
||||
public bool IsPrimeCpu(ulong n)
|
||||
{
|
||||
return IsPrime(n, CancellationToken.None);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsPrimeGpu(ulong n, CancellationToken ct)
|
||||
{
|
||||
if (!EnableHeuristicPrimeTesting)
|
||||
{
|
||||
return PrimeTester.Exclusive.IsPrimeGpu(n, ct);
|
||||
}
|
||||
|
||||
return IsPrimeGpu(n);
|
||||
return IsPrimeCpu(n, CancellationToken.None);
|
||||
}
|
||||
|
||||
public bool IsPrimeGpu(ulong n)
|
||||
@@ -117,45 +111,9 @@ public sealed class HeuristicPrimeTester
|
||||
|
||||
public bool IsPrimeGpu(ulong n, ulong sqrtLimit, byte nMod10)
|
||||
{
|
||||
if (!EnableHeuristicPrimeTesting)
|
||||
{
|
||||
return PrimeTester.Exclusive.IsPrimeGpu(n, CancellationToken.None);
|
||||
}
|
||||
|
||||
return HeuristicIsPrimeGpuCore(n, sqrtLimit, nMod10);
|
||||
}
|
||||
|
||||
public bool HeuristicIsPrimeGpu(ulong n)
|
||||
{
|
||||
return IsPrimeGpu(n);
|
||||
}
|
||||
|
||||
public static bool HeuristicIsPrimeCpu(ulong n)
|
||||
{
|
||||
return HeuristicIsPrimeCpu(n, 0UL, (byte)n.Mod10());
|
||||
}
|
||||
|
||||
public static bool HeuristicIsPrimeCpu(ulong n, ulong sqrtLimit, byte nMod10)
|
||||
{
|
||||
if (!EnableHeuristicPrimeTesting)
|
||||
{
|
||||
return PrimeTester.IsPrimeInternal(n, CancellationToken.None);
|
||||
}
|
||||
|
||||
if (sqrtLimit == 0UL)
|
||||
{
|
||||
sqrtLimit = ComputeHeuristicSqrt(n);
|
||||
}
|
||||
|
||||
if (sqrtLimit < 3UL)
|
||||
{
|
||||
return EvaluateWithOpenNumericFallback(n);
|
||||
}
|
||||
|
||||
bool includeGroupB = UseHeuristicGroupBTrialDivision;
|
||||
return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10, includeGroupB);
|
||||
}
|
||||
|
||||
private bool HeuristicIsPrimeGpuCore(ulong n, ulong sqrtLimit, byte nMod10)
|
||||
{
|
||||
// TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner?
|
||||
@@ -169,11 +127,6 @@ public sealed class HeuristicPrimeTester
|
||||
return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10, includeGroupB: false);
|
||||
}
|
||||
|
||||
if (GpuContextPool.ForceCpu)
|
||||
{
|
||||
return HeuristicIsPrimeCpu(n, sqrtLimit, nMod10);
|
||||
}
|
||||
|
||||
bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, sqrtLimit, nMod10);
|
||||
return !compositeDetected;
|
||||
}
|
||||
@@ -304,6 +257,7 @@ public sealed class HeuristicPrimeTester
|
||||
|
||||
|
||||
|
||||
|
||||
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10)
|
||||
{
|
||||
int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize);
|
||||
@@ -311,194 +265,165 @@ public sealed class HeuristicPrimeTester
|
||||
var divisorPool = ThreadStaticPools.UlongPool;
|
||||
var hitPool = ThreadStaticPools.BytePool;
|
||||
|
||||
ulong[]? divisorArray = null;
|
||||
byte[]? hitFlags = null;
|
||||
ulong[] divisorArray = divisorPool.Rent(batchCapacity);
|
||||
byte[] hitFlags = hitPool.Rent(batchCapacity);
|
||||
|
||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent();
|
||||
var accelerator = gpu.Accelerator;
|
||||
var state = PrimeTester.GpuKernelState.GetOrCreate(accelerator);
|
||||
var scratch = state.RentScratch(batchCapacity, accelerator);
|
||||
|
||||
bool compositeDetected = false;
|
||||
int count = 0;
|
||||
|
||||
try
|
||||
lock (gpu.ExecutionLock)
|
||||
{
|
||||
divisorArray = divisorPool.Rent(batchCapacity);
|
||||
hitFlags = hitPool.Rent(batchCapacity);
|
||||
var input = scratch.Input;
|
||||
var output = scratch.Output;
|
||||
|
||||
var accelerator = gpu.Accelerator;
|
||||
var state = PrimeTester.GpuKernelState.GetOrCreate(accelerator);
|
||||
|
||||
lock (gpu.ExecutionLock)
|
||||
bool ProcessBatch(int length)
|
||||
{
|
||||
var scratch = state.RentScratch(batchCapacity, accelerator);
|
||||
try
|
||||
if (length <= 0)
|
||||
{
|
||||
int count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessBatch(int length)
|
||||
input.View.CopyFromCPU(ref divisorArray[0], length);
|
||||
state.HeuristicTrialDivisionKernel(length, input.View, n, output.View);
|
||||
accelerator.Synchronize();
|
||||
output.View.CopyToCPU(ref hitFlags[0], length);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (hitFlags[i] == 0)
|
||||
{
|
||||
if (length <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
scratch.Input.View.CopyFromCPU(ref divisorArray![0], length);
|
||||
state.HeuristicTrialDivisionKernel(length, scratch.Input.View, n, scratch.Output.View);
|
||||
accelerator.Synchronize();
|
||||
scratch.Output.View.CopyToCPU(ref hitFlags![0], length);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (hitFlags![i] == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong divisor = divisorArray![i];
|
||||
if (divisor > 1UL && n % divisor == 0UL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool QueueDivisor(ulong divisor)
|
||||
ulong divisor = divisorArray[i];
|
||||
if (divisor > 1UL && n % divisor == 0UL)
|
||||
{
|
||||
divisorArray![count] = divisor;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors;
|
||||
int groupAIndex = 0;
|
||||
ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10);
|
||||
Span<int> indices = endingOrder.Length <= 8
|
||||
? stackalloc int[endingOrder.Length]
|
||||
: new int[endingOrder.Length];
|
||||
|
||||
bool groupAHasMore = groupAIndex < groupADivisors.Length && (ulong)groupADivisors[groupAIndex] <= sqrtLimit;
|
||||
bool groupBHasMore = !endingOrder.IsEmpty && HasGroupBCandidates(endingOrder, indices, sqrtLimit);
|
||||
|
||||
while (!compositeDetected && (groupAHasMore || groupBHasMore))
|
||||
{
|
||||
if (groupAHasMore)
|
||||
{
|
||||
int processed = 0;
|
||||
while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length)
|
||||
{
|
||||
ulong divisor = (ulong)groupADivisors[groupAIndex];
|
||||
if (divisor > sqrtLimit)
|
||||
{
|
||||
groupAIndex = groupADivisors.Length;
|
||||
groupAHasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
divisorArray[count] = divisor;
|
||||
count++;
|
||||
|
||||
if (count == batchCapacity)
|
||||
{
|
||||
if (ProcessBatch(count))
|
||||
{
|
||||
return true;
|
||||
compositeDetected = true;
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
groupAIndex++;
|
||||
processed++;
|
||||
}
|
||||
|
||||
ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors;
|
||||
int groupAIndex = 0;
|
||||
ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10);
|
||||
Span<int> indices = endingOrder.Length <= 8
|
||||
? stackalloc int[endingOrder.Length]
|
||||
: new int[endingOrder.Length];
|
||||
|
||||
bool groupAHasMore = groupAIndex < groupADivisors.Length && (ulong)groupADivisors[groupAIndex] <= sqrtLimit;
|
||||
bool groupBHasMore = !endingOrder.IsEmpty && HasGroupBCandidates(endingOrder, indices, sqrtLimit);
|
||||
|
||||
while (!compositeDetected && (groupAHasMore || groupBHasMore))
|
||||
if (groupAIndex >= groupADivisors.Length)
|
||||
{
|
||||
if (groupAHasMore)
|
||||
{
|
||||
int processed = 0;
|
||||
while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length)
|
||||
{
|
||||
ulong divisor = (ulong)groupADivisors[groupAIndex];
|
||||
if (divisor > sqrtLimit)
|
||||
{
|
||||
groupAIndex = groupADivisors.Length;
|
||||
groupAHasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
compositeDetected = QueueDivisor(divisor);
|
||||
if (compositeDetected)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
groupAIndex++;
|
||||
processed++;
|
||||
}
|
||||
|
||||
if (compositeDetected)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (groupAIndex >= groupADivisors.Length)
|
||||
{
|
||||
groupAHasMore = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
groupAHasMore = (ulong)groupADivisors[groupAIndex] <= sqrtLimit;
|
||||
if (!groupAHasMore)
|
||||
{
|
||||
groupAIndex = groupADivisors.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (groupBHasMore)
|
||||
{
|
||||
int processed = 0;
|
||||
while (processed < interleaveBatchSize)
|
||||
{
|
||||
if (!TrySelectNextGroupBDivisor(endingOrder, indices, sqrtLimit, out ulong divisor))
|
||||
{
|
||||
groupBHasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
compositeDetected = QueueDivisor(divisor);
|
||||
if (compositeDetected)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
processed++;
|
||||
}
|
||||
|
||||
if (compositeDetected)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (groupBHasMore)
|
||||
{
|
||||
groupBHasMore = HasGroupBCandidates(endingOrder, indices, sqrtLimit);
|
||||
}
|
||||
}
|
||||
groupAHasMore = false;
|
||||
}
|
||||
|
||||
if (!compositeDetected && count > 0)
|
||||
else
|
||||
{
|
||||
compositeDetected = ProcessBatch(count);
|
||||
groupAHasMore = (ulong)groupADivisors[groupAIndex] <= sqrtLimit;
|
||||
if (!groupAHasMore)
|
||||
{
|
||||
groupAIndex = groupADivisors.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
if (!compositeDetected && groupBHasMore)
|
||||
{
|
||||
state.ReturnScratch(scratch);
|
||||
int processed = 0;
|
||||
while (processed < interleaveBatchSize)
|
||||
{
|
||||
if (!TrySelectNextGroupBDivisor(endingOrder, indices, sqrtLimit, out ulong divisor))
|
||||
{
|
||||
groupBHasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
divisorArray[count] = divisor;
|
||||
count++;
|
||||
|
||||
if (count == batchCapacity)
|
||||
{
|
||||
if (ProcessBatch(count))
|
||||
{
|
||||
compositeDetected = true;
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
}
|
||||
|
||||
processed++;
|
||||
}
|
||||
|
||||
if (groupBHasMore)
|
||||
{
|
||||
groupBHasMore = HasGroupBCandidates(endingOrder, indices, sqrtLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (divisorArray is not null)
|
||||
{
|
||||
divisorPool.Return(divisorArray);
|
||||
}
|
||||
|
||||
if (hitFlags is not null)
|
||||
if (!compositeDetected && count > 0)
|
||||
{
|
||||
hitPool.Return(hitFlags);
|
||||
}
|
||||
if (ProcessBatch(count))
|
||||
{
|
||||
compositeDetected = true;
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
gpu.Dispose();
|
||||
limiter.Dispose();
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Cleanup:
|
||||
state.ReturnScratch(scratch);
|
||||
divisorPool.Return(divisorArray);
|
||||
hitPool.Return(hitFlags);
|
||||
gpu.Dispose();
|
||||
limiter.Dispose();
|
||||
return compositeDetected;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static bool EvaluateWithOpenNumericFallback(ulong n)
|
||||
{
|
||||
return Prime.Numbers.IsPrime(n);
|
||||
|
||||
@@ -394,9 +394,9 @@ public class MersenneDivisorCycles
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Benchmark PrimeTester.IsPrimeInternal against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites.
|
||||
// if (HeuristicPrimeTester.Exclusive.IsPrime(remaining, CancellationToken.None))
|
||||
if (PrimeTester.IsPrimeInternal(remaining, CancellationToken.None))
|
||||
// TODO: Benchmark PrimeTester.IsPrimeCp against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites.
|
||||
// if (HeuristicPrimeTester.Exclusive.IsPrimeCpu(remaining, CancellationToken.None))
|
||||
if (PrimeTester.IsPrimeCp(remaining, CancellationToken.None))
|
||||
{
|
||||
AddFactor(counts, remaining);
|
||||
return true;
|
||||
@@ -928,8 +928,8 @@ public class MersenneDivisorCycles
|
||||
return true;
|
||||
}
|
||||
|
||||
// if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrime(divisor, CancellationToken.None))
|
||||
if (!skipPrimeOrderHeuristic && PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
||||
// if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrimeCpu(divisor, CancellationToken.None))
|
||||
if (!skipPrimeOrderHeuristic && PrimeTester.IsPrimeCp(divisor, CancellationToken.None))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
||||
divisor,
|
||||
|
||||
@@ -25,8 +25,8 @@ public static class MersennePrimeFactorTester
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (!HeuristicPrimeTester.Exclusive.IsPrime(q, ct))
|
||||
if (!PrimeTester.IsPrimeInternal(q, ct))
|
||||
// if (!HeuristicPrimeTester.Exclusive.IsPrimeCpu(q, ct))
|
||||
if (!PrimeTester.IsPrimeCp(q, ct))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -234,8 +234,8 @@ public static class MersennePrimeFactorTester
|
||||
continue;
|
||||
}
|
||||
|
||||
// if (HeuristicPrimeTester.Exclusive.IsPrime(m, ct))
|
||||
if (PrimeTester.IsPrimeInternal(m, ct))
|
||||
// if (HeuristicPrimeTester.Exclusive.IsPrimeCpu(m, ct))
|
||||
if (PrimeTester.IsPrimeCp(m, ct))
|
||||
{
|
||||
dict[m] = 1;
|
||||
continue;
|
||||
|
||||
@@ -516,7 +516,7 @@ internal static partial class PrimeOrderCalculator
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (!PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None))
|
||||
// if (!PrimeTester.IsPrimeCp(orderFactors.Cofactor, CancellationToken.None))
|
||||
if (!orderFactors.CofactorIsPrime)
|
||||
{
|
||||
return false;
|
||||
@@ -1123,8 +1123,8 @@ internal static partial class PrimeOrderCalculator
|
||||
// }
|
||||
|
||||
// bool isPrime = primeTester.HeuristicIsPrimeGpu(composite);
|
||||
// bool isPrime = PrimeTester.IsPrimeInternal(composite, CancellationToken.None);
|
||||
bool isPrime = PrimeTester.IsPrimeInternal(composite, CancellationToken.None);
|
||||
// bool isPrime = PrimeTester.IsPrimeCp(composite, CancellationToken.None);
|
||||
bool isPrime = PrimeTester.IsPrimeCp(composite, CancellationToken.None);
|
||||
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite);
|
||||
|
||||
if (isPrime)
|
||||
@@ -1186,8 +1186,8 @@ internal static partial class PrimeOrderCalculator
|
||||
if (!entry.HasKnownPrimality)
|
||||
{
|
||||
// bool isPrime = primeTester.HeuristicIsPrimeGpu(composite);
|
||||
// bool isPrime = PrimeTester.IsPrimeInternal(composite, CancellationToken.None);
|
||||
bool isPrime = PrimeTester.IsPrimeInternal(composite, CancellationToken.None);
|
||||
// bool isPrime = PrimeTester.IsPrimeCp(composite, CancellationToken.None);
|
||||
bool isPrime = PrimeTester.IsPrimeCp(composite, CancellationToken.None);
|
||||
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite);
|
||||
|
||||
entry = entry.WithPrimality(isPrime);
|
||||
@@ -1219,8 +1219,8 @@ internal static partial class PrimeOrderCalculator
|
||||
else
|
||||
{
|
||||
// cofactorIsPrime = primeTester.HeuristicIsPrimeGpu(cofactor);
|
||||
// cofactorIsPrime = PrimeTester.IsPrimeInternal(cofactor, CancellationToken.None);
|
||||
cofactorIsPrime = PrimeTester.IsPrimeInternal(cofactor, CancellationToken.None);
|
||||
// cofactorIsPrime = PrimeTester.IsPrimeCp(cofactor, CancellationToken.None);
|
||||
cofactorIsPrime = PrimeTester.IsPrimeCp(cofactor, CancellationToken.None);
|
||||
// cofactorIsPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(cofactor);
|
||||
}
|
||||
|
||||
@@ -1752,8 +1752,8 @@ internal static partial class PrimeOrderCalculator
|
||||
PrimeTester primeTester = PrimeTester.Exclusive;
|
||||
|
||||
// bool isPrime = primeTester.HeuristicIsPrimeGpu(value);
|
||||
// bool isPrime = PrimeTester.IsPrimeInternal(value, CancellationToken.None);
|
||||
bool isPrime = PrimeTester.IsPrimeInternal(value, CancellationToken.None);
|
||||
// bool isPrime = PrimeTester.IsPrimeCp(value, CancellationToken.None);
|
||||
bool isPrime = PrimeTester.IsPrimeCp(value, CancellationToken.None);
|
||||
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(value);
|
||||
if (!knownComposite && isPrime)
|
||||
{
|
||||
@@ -1765,8 +1765,8 @@ internal static partial class PrimeOrderCalculator
|
||||
ulong quotient = value / factor;
|
||||
|
||||
// isPrime = primeTester.HeuristicIsPrimeGpu(factor);
|
||||
// isPrime = PrimeTester.IsPrimeInternal(factor, CancellationToken.None);
|
||||
isPrime = PrimeTester.IsPrimeInternal(factor, CancellationToken.None);
|
||||
// isPrime = PrimeTester.IsPrimeCp(factor, CancellationToken.None);
|
||||
isPrime = PrimeTester.IsPrimeCp(factor, CancellationToken.None);
|
||||
// isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factor);
|
||||
if (isPrime)
|
||||
{
|
||||
@@ -1797,8 +1797,8 @@ internal static partial class PrimeOrderCalculator
|
||||
else
|
||||
{
|
||||
// isPrime = primeTester.HeuristicIsPrimeGpu(quotient);
|
||||
// isPrime = PrimeTester.IsPrimeInternal(quotient, CancellationToken.None);
|
||||
isPrime = PrimeTester.IsPrimeInternal(quotient, CancellationToken.None);
|
||||
// isPrime = PrimeTester.IsPrimeCp(quotient, CancellationToken.None);
|
||||
isPrime = PrimeTester.IsPrimeCp(quotient, CancellationToken.None);
|
||||
// isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(quotient);
|
||||
}
|
||||
|
||||
|
||||
@@ -955,8 +955,8 @@ internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
if (value <= ulong.MaxValue)
|
||||
{
|
||||
// return HeuristicPrimeTester.Exclusive.IsPrime((ulong)value, CancellationToken.None);
|
||||
return PrimeTester.IsPrimeInternal((ulong)value, CancellationToken.None);
|
||||
// return HeuristicPrimeTester.Exclusive.IsPrimeCpu((ulong)value, CancellationToken.None);
|
||||
return PrimeTester.IsPrimeCp((ulong)value, CancellationToken.None);
|
||||
}
|
||||
|
||||
if ((value & UInt128.One) == UInt128.Zero)
|
||||
|
||||
@@ -24,29 +24,27 @@ public sealed class PrimeTester
|
||||
|
||||
public bool IsPrime(ulong n, CancellationToken ct)
|
||||
{
|
||||
// EvenPerfectBitScanner feeds monotonically increasing odd exponents that start well above the
|
||||
// small-prime cutoffs and already exclude multiples of five. These guards remain for targeted
|
||||
// tests and diagnostics so ad-hoc callers can still probe small or even values when needed.
|
||||
if (n <= 1UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// bool isTwo = n == 2UL;
|
||||
if (n == 2UL)
|
||||
{
|
||||
throw new InvalidOperationException("PrimeTester.IsPrime encountered the sentinel input 2.");
|
||||
}
|
||||
|
||||
bool isOdd = (n & 1UL) != 0UL;
|
||||
// ulong mod5 = n % 5UL;
|
||||
// bool divisibleByFive = n > 5UL && mod5 == 0UL;
|
||||
bool result = isOdd;
|
||||
|
||||
bool result = /*n >= 2UL && (isTwo || */ (n == 2UL || isOdd) /*) && !divisibleByFive*/;
|
||||
|
||||
// Production scans always fall through to trial division because n is already > 5 by the time this
|
||||
// path executes. Keep the guard so synthetic callers that hammer tiny inputs can skip the loop.
|
||||
bool requiresTrialDivision = result /*&& n >= 7UL && !isTwo*/ && n >= 7UL;
|
||||
bool requiresTrialDivision = result && n >= 7UL;
|
||||
|
||||
if (requiresTrialDivision)
|
||||
{
|
||||
bool sharesMaxExponentFactor = n.Mod10() == 1UL && SharesFactorWithMaxExponent(n);
|
||||
result &= !sharesMaxExponentFactor;
|
||||
// EvenPerfectBitScanner streams exponents starting at 136,279,841, so the Mod10/GCD guard never fires on the
|
||||
// production path. Leave the logic commented out as instrumentation for diagnostic builds.
|
||||
// bool sharesMaxExponentFactor = n.Mod10() == 1UL && SharesFactorWithMaxExponent(n);
|
||||
// result &= !sharesMaxExponentFactor;
|
||||
|
||||
if (result)
|
||||
{
|
||||
@@ -95,10 +93,10 @@ public sealed class PrimeTester
|
||||
bool gpuReportedPrime = !forceCpu && !belowGpuRange && outFlags[0] != 0;
|
||||
bool requiresCpuFallback = forceCpu || belowGpuRange || !gpuReportedPrime;
|
||||
|
||||
return requiresCpuFallback ? IsPrimeInternal(n, ct) : true;
|
||||
return requiresCpuFallback ? IsPrimeCp(n, ct) : true;
|
||||
}
|
||||
|
||||
public static bool IsPrimeInternal(ulong n, CancellationToken ct)
|
||||
public static bool IsPrimeCp(ulong n, CancellationToken ct)
|
||||
{
|
||||
// Preserve the legacy entry point for callers that bypass the thread-local caches.
|
||||
return Exclusive.IsPrime(n, ct);
|
||||
|
||||
@@ -64,12 +64,12 @@ public bool IsPrimeGpu(ulong n, CancellationToken ct)
|
||||
bool gpuReportedPrime = !forceCpu && !belowGpuRange && outFlags[0] != 0;
|
||||
bool requiresCpuFallback = forceCpu || belowGpuRange || !gpuReportedPrime;
|
||||
|
||||
return requiresCpuFallback ? IsPrimeInternal(n, ct) : true;
|
||||
return requiresCpuFallback ? IsPrimeCp(n, ct) : true;
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
internal static bool IsPrimeInternal(ulong n, CancellationToken ct)
|
||||
internal static bool IsPrimeCp(ulong n, CancellationToken ct)
|
||||
{
|
||||
bool isTwo = n == 2UL;
|
||||
bool isOdd = (n & 1UL) != 0UL;
|
||||
@@ -113,11 +113,11 @@ internal static bool IsPrimeInternal(ulong n, CancellationToken ct)
|
||||
|
||||
The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels, residue priorities) and connects them to both CPU and GPU execution, including every by-divisor integration point. Items already completed are marked with `[done]`.
|
||||
|
||||
1. [done] **Snapshot current implementations** – preserve `IsPrimeGpu` and `IsPrimeInternal` as references for regression comparisons and migration checkpoints (see Section 2).
|
||||
2. [done] **Introduce placeholder heuristic entry points** – add `HeuristicIsPrimeCpu` and `HeuristicIsPrimeGpu` by copying the current internal and GPU methods so subsequent commits can iterate without disrupting legacy behavior.
|
||||
1. [done] **Snapshot current implementations** – preserve `IsPrimeGpu` and `IsPrimeCp` as references for regression comparisons and migration checkpoints (see Section 2).
|
||||
2. [done] **Introduce placeholder heuristic entry points** – add `IsPrimeCpu` and `IsPrimeGpu` by copying the current internal and GPU methods so subsequent commits can iterate without disrupting legacy behavior.
|
||||
3. [done] **Design shared heuristic scaffolding**
|
||||
* [done] Added `HeuristicPrimeSieves` to precompute 4M-entry Group A/B divisor tables so heuristic prime checks reuse cached sequences without regenerating wheel steps at runtime; the enumerator remains available for residue-driven consumers.
|
||||
* [done] Surface helpers that accept the precomputed `R = ⌊√n⌋` and `nMod10` values from callers via the new overload of `HeuristicIsPrimeCpu`; HeuristicPrimeTester still computes them locally until call sites forward the parameters.
|
||||
* [done] Surface helpers that accept the precomputed `R = ⌊√n⌋` and `nMod10` values from callers via the new overload of `IsPrimeCpu`; HeuristicPrimeTester still computes them locally until call sites forward the parameters.
|
||||
* [done] Simplified the heuristic execution path so it runs without cancellation tokens or callback plumbing while keeping the shared enumerator available to other components.
|
||||
4. **Share residue and wheel state with by-divisor sessions**
|
||||
* [done] Extracted the rolling-modulus helpers (mod 10/8/5/3/7/11) and decimal masks from `MersenneNumberDivisorByDivisorCpuTester.CheckDivisors` into the shared `MersenneDivisorResidueStepper` so heuristic prime tests and by-divisor sessions consume the same filters.
|
||||
@@ -126,11 +126,11 @@ The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels,
|
||||
5. **Expose cycle-preparation helpers**
|
||||
* [done] Extend the heuristic scaffolding so it returns `MontgomeryDivisorData` descriptors and cycle-length hints for each candidate divisor via `HeuristicDivisorPreparation` and `PrepareHeuristicDivisor`.
|
||||
* Wire these outputs into the existing cycle calculators to avoid duplicate cache lookups inside the CPU/GPU divisor scans.
|
||||
6. [done] **Implement `HeuristicIsPrimeCpu`**
|
||||
* Baseline Group A/B enumeration now executes inside `HeuristicIsPrimeCpu` via the cached `HeuristicPrimeSieves` tables, computing `⌊√n⌋` locally and short-circuiting on the first divisor.
|
||||
6. [done] **Implement `IsPrimeCpu`**
|
||||
* Baseline Group A/B enumeration now executes inside `IsPrimeCpu` via the cached `HeuristicPrimeSieves` tables, computing `⌊√n⌋` locally and short-circuiting on the first divisor.
|
||||
* [done] The CPU heuristic now operates as a pure trial-division sweep without maintaining per-call summaries, relying on `PrepareHeuristicDivisor` only when downstream consumers request Montgomery data.
|
||||
* [done] Introduced the temporary `UseHeuristicGroupBTrialDivision` gate so current builds execute Group A locally and then fall back to `Open.Numeric.Primes.Prime.Numbers.IsPrime`, keeping the full Group B implementation available for reactivation.
|
||||
7. [done] **Implement `HeuristicIsPrimeGpu`**
|
||||
7. [done] **Implement `IsPrimeGpu`**
|
||||
* [done] Mirror CPU structure while batching divisor checks on the accelerator via `HeuristicTrialDivisionGpu`, drawing divisors from `HeuristicPrimeSieves`, reusing caller-provided `sqrtLimit`/`nMod10` inputs, and falling back to CPU when GPUs are disabled.
|
||||
* [done] Added `PrimeTesterKernels.HeuristicTrialDivisionKernel` so GPU batches respect Group A/B ordering while emitting hit flags for early exits.
|
||||
* [done] Kept GPU divisibility in standard modular arithmetic while reserving Montgomery transforms for CPU confirmation logic.
|
||||
@@ -140,12 +140,12 @@ The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels,
|
||||
* [done] Streamed GPU batches from the same enumerator and Montgomery preparation data, removing the filtered-divisor scratch arrays while continuing to feed the existing kernel and hit bookkeeping.
|
||||
9. [done] **Route legacy prime checks through heuristics**
|
||||
* [done] Introduce `HeuristicPrimeTester` so heuristic CPU/GPU checks live alongside the legacy `PrimeTester` implementations while callers opt in explicitly.
|
||||
* [done] Maintain legacy paths for benchmarking via `PrimeTester.IsPrimeInternal`, `PrimeTester.Exclusive.IsPrimeGpu`, and the global `HeuristicPrimeTester.EnableHeuristicPrimeTesting` switch so benchmark harnesses can compare behaviours.
|
||||
* [done] Maintain legacy paths for benchmarking via `PrimeTester.IsPrimeCp`, `PrimeTester.Exclusive.IsPrimeGpu`, and the explicit `HeuristicPrimeTester` entry points so benchmark harnesses can compare behaviours without runtime switches.
|
||||
10. **Validation and benchmarking**
|
||||
* Create targeted unit tests covering boundary cases for Group A/B transitions, wheel enumerations, residue deltas, and the stop-on-hit behaviour.
|
||||
* Expand GPU test harnesses to assert equivalence between CPU/GPU heuristics on sampled composite and prime inputs and to verify the residue-stream contract with by-divisor sessions.
|
||||
* Benchmark against historical data (hundreds of thousands of composites) to confirm that heuristic ordering improves early divisor detection and that residue sharing reduces duplicate computation.
|
||||
* [done] Build BenchmarkDotNet comparisons for `HeuristicPrimeTester.IsPrimeGpu`, `HeuristicPrimeTester.HeuristicIsPrimeCpu`, and `Open.Numeric.Primes.Prime.Numbers.IsPrime` across representative ranges (≤100, ≤4,000,000, and ≥138,000,000) before routing heuristic code into production. Implemented in `EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs` to exercise both CPU and GPU heuristic paths alongside the external library.
|
||||
* [done] Build BenchmarkDotNet comparisons for `HeuristicPrimeTester.IsPrimeGpu`, `HeuristicPrimeTester.IsPrimeCpu`, and `Open.Numeric.Primes.Prime.Numbers.IsPrime` across representative ranges (≤100, ≤4,000,000, and ≥138,000,000) before routing heuristic code into production. Implemented in `EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs` to exercise both CPU and GPU heuristic paths alongside the external library.
|
||||
11. **Documentation and rollout**
|
||||
* Update README/docs to explain heuristic prerequisites (odd, not divisible by 5) and how configuration toggles affect execution across prime testing and by-divisor scans.
|
||||
* Provide migration guidance for operators toggling between legacy and heuristic modes and describe how telemetry/metrics change after integration.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Heuristic Prime Tester Specification
|
||||
|
||||
This document describes the heuristic prime testing procedure implemented by `HeuristicPrimeTester.HeuristicIsPrimeCpu` and `HeuristicPrimeTester.IsPrimeGpu`. The guidance consolidates the residue analysis and empirical statistics for odd inputs that are not divisible by five, based on the first 500,000 odd composite numbers after removing multiples of five. It also records the integration contracts required by the by-divisor pipeline so the heuristic covers every path that participates in divisor searches, residue sieves, and factorisation checks.
|
||||
This document describes the heuristic prime testing procedure implemented by `HeuristicPrimeTester.IsPrimeCpu` and `HeuristicPrimeTester.IsPrimeGpu`. The guidance consolidates the residue analysis and empirical statistics for odd inputs that are not divisible by five, based on the first 500,000 odd composite numbers after removing multiples of five. It also records the integration contracts required by the by-divisor pipeline so the heuristic covers every path that participates in divisor searches, residue sieves, and factorisation checks.
|
||||
|
||||
## Input Assumptions
|
||||
|
||||
@@ -57,23 +57,23 @@ No other endings are allowed or reordered.
|
||||
4. **Group B**: read the precomputed `n mod 10` (made available by the caller configuration such as `--gpu-prime-batch`). Iterate candidates via wheel 210, maintaining ascending order and applying the ending-priority sequence above. Any hit marks `n` as composite and terminates the search.
|
||||
5. **Decision**: if no divisors were found up to `R`, report `n` as “prime by trial”. This outcome is final—no further confirmation runs are performed.
|
||||
|
||||
> **Temporary status:** Current builds disable Group B via `UseHeuristicGroupBTrialDivision`. After exhausting Group A, `HeuristicIsPrime*` delegates to `Open.Numeric.Primes.Prime.Numbers.IsPrime`, preserving the Group B implementation for future use while ensuring production checks mirror the external library once Group A filters pass.
|
||||
> **Temporary status:** Current builds disable Group B via `UseHeuristicGroupBTrialDivision`. After exhausting Group A, `HeuristicPrimeTester.IsPrime*` delegates to `Open.Numeric.Primes.Prime.Numbers.IsPrime`, preserving the Group B implementation for future use while ensuring production checks mirror the external library once Group A filters pass.
|
||||
|
||||
## Default Prime Tester Entry Points
|
||||
|
||||
* `PrimeTester.IsPrime` and `PrimeTester.IsPrimeGpu` continue to run the legacy trial-division and GPU sieve paths. Use `HeuristicPrimeTester` when the heuristic CPU/GPU flow is required.
|
||||
* Toggle `HeuristicPrimeTester.EnableHeuristicPrimeTesting` or fall back to `PrimeTester.IsPrimeInternal` / `PrimeTester.Exclusive.IsPrimeGpu` to control heuristic rollouts in test harnesses.
|
||||
* `HeuristicPrimeTester` always executes the heuristic CPU/GPU implementations; opt into the legacy paths by calling `PrimeTester.IsPrimeCp` or `PrimeTester.Exclusive.IsPrimeGpu` directly.
|
||||
* Benchmark harnesses can compare behaviours by calling the legacy helpers on `PrimeTester` alongside the heuristic equivalents on `HeuristicPrimeTester`.
|
||||
|
||||
## Integration with the By-Divisor Pipeline
|
||||
|
||||
The by-divisor executors (`MersenneNumberDivisorByDivisorCpuTester` and `MersenneNumberDivisorByDivisorGpuTester`) reuse prime heuristics to seed their divisor scans, residue sieves, and cycle computations. The heuristic implementations must therefore expose the following data and behaviours:
|
||||
|
||||
* **Residue filters** – The CPU scan maintains rolling residues modulo 10/8/5/3/7/11 to match the filters in `MersenneCpuDivisorScanSession.CheckDivisors`. `HeuristicIsPrime*` must either share the residue update helpers or produce identical stepping deltas so the divisor scanners can reuse the pre-filtered candidate stream without recomputing moduli. The shared `MersenneDivisorResidueStepper` now centralises those modulo calculations so both paths reuse the same state transitions, and the GPU by-divisor tester instantiates the same stepper before filtering candidates.
|
||||
* **Residue filters** – The CPU scan maintains rolling residues modulo 10/8/5/3/7/11 to match the filters in `MersenneCpuDivisorScanSession.CheckDivisors`. `HeuristicPrimeTester.IsPrime*` must either share the residue update helpers or produce identical stepping deltas so the divisor scanners can reuse the pre-filtered candidate stream without recomputing moduli. The shared `MersenneDivisorResidueStepper` now centralises those modulo calculations so both paths reuse the same state transitions, and the GPU by-divisor tester instantiates the same stepper before filtering candidates.
|
||||
* **Stepper integration** – CPU sessions advance residues and unity checks through `ExponentRemainderStepperCpu` together with `CycleRemainderStepper`; GPU kernels mirror this contract via `ExponentRemainderStepperGpu`. The heuristic implementation must call into these steppers (or emit the same remainder deltas) so the shared stepping state remains aligned across CPU/GPU scans and factor validation.
|
||||
* **Wheel alignment** – `HeuristicPrimeSieves` precomputes the wheel 30/210 sequences once at startup so both the heuristic prime tester and the by-divisor executors consume identical Group A/B streams without regenerating residues on every call.
|
||||
* **Divisor streaming API** – `HeuristicPrimeTester.CreateHeuristicDivisorEnumerator` exposes the same precomputed sieves so by-divisor scans can pull candidates directly into Montgomery preparation and GPU kernels without bespoke residue loops.
|
||||
* **GPU batching** – `HeuristicIsPrimeGpu` streams Group A/B candidates from `HeuristicPrimeSieves`, packs them into batches controlled by `HeuristicPrimeTester.HeuristicGpuDivisorBatchSize`, and launches `PrimeTesterKernels.HeuristicTrialDivisionKernel` to test each divisor with standard `%` arithmetic. When GPUs are disabled, the method delegates to the CPU heuristic so fallbacks reuse the same candidate ordering without additional plumbing.
|
||||
* **GPU batching** – `HeuristicPrimeTester.IsPrimeGpu` streams Group A/B candidates from `HeuristicPrimeSieves`, packs them into batches controlled by `HeuristicPrimeTester.HeuristicGpuDivisorBatchSize`, and launches `PrimeTesterKernels.HeuristicTrialDivisionKernel` to test each divisor with standard `%` arithmetic. The implementation now assumes the accelerator path is available and no longer routes through the CPU fallback.
|
||||
* **Cycle preparation** – When the heuristic discovers a divisor candidate, it should populate `MontgomeryDivisorData` and expose the computed cycle length (or the need to recompute it) so that `DivisorCycleCache` and `MersenneDivisorCycles` do not duplicate work for the same `(prime, divisor)` pair during factor verification. `HeuristicPrimeTester.PrepareHeuristicDivisor` together with `HeuristicPrimeTester.ResolveHeuristicCycleLength` now provides this payload to both CPU and GPU by-divisor scans, and the trial-division summaries capture the data whenever a divisor is found.
|
||||
|
||||
* **Caching contract** – The by-divisor GPU implementation pools `DivisorScanSession` instances and reuses GPU buffers. The heuristic must accept externally allocated spans/buffers for divisor candidates, cycle lengths, and hits so the existing resource pools remain valid.
|
||||
@@ -103,5 +103,5 @@ The following summaries cover the first 500,000 odd composite numbers after remo
|
||||
|
||||
## Benchmark Reporting
|
||||
|
||||
Benchmark results comparing `HeuristicPrimeTester.IsPrimeGpu`, `HeuristicPrimeTester.HeuristicIsPrimeCpu`, and `Open.Numeric.Primes.Prime.Numbers.IsPrime` will be documented here once the dedicated benchmarks covering ≤100, ≤4,000,000, and ≥138,000,000 candidates are executed. The specification will be updated with the measured timings and analysis so operators can evaluate heuristic rollouts against legacy and external implementations. Benchmark harnesses are available via `EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs`, which runs the heuristic CPU and GPU paths alongside `Open.Numeric.Primes` for the three target ranges.
|
||||
Benchmark results comparing `HeuristicPrimeTester.IsPrimeGpu`, `HeuristicPrimeTester.IsPrimeCpu`, and `Open.Numeric.Primes.Prime.Numbers.IsPrime` will be documented here once the dedicated benchmarks covering ≤100, ≤4,000,000, and ≥138,000,000 candidates are executed. The specification will be updated with the measured timings and analysis so operators can evaluate heuristic rollouts against legacy and external implementations. Benchmark harnesses are available via `EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs`, which runs the heuristic CPU and GPU paths alongside `Open.Numeric.Primes` for the three target ranges.
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ Divisor Cycle Cache & Generation
|
||||
- PerfectNumbers.Core/UInt128Extensions.cs:372: If the cache lacks this cycle, immediately schedule the configured device (GPU by default) to compute it on the fly and skip inserting it into the cache so wide-order factoring can still leverage cycle stepping without breaching the memory cap or introducing extra synchronization.
|
||||
|
||||
Primality & Order Screening
|
||||
- PerfectNumbers.Core/MersenneDivisorCycles.cs:477: Benchmark PrimeTester.IsPrimeInternal against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites.
|
||||
- PerfectNumbers.Core/MersenneDivisorCycles.cs:477: Benchmark PrimeTester.IsPrimeCp against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites.
|
||||
- PerfectNumbers.Core/ULongExtensions.cs:119: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand GPU computation (respecting the configured device) without promoting the result into the cache so the order calculator still benefits from cycle stepping while keeping the single-block memory plan intact.
|
||||
- PerfectNumbers.Core/ULongExtensions.cs:132: Replace this `%` driven factor peeling with the divisor-cycle aware factoring helper so large orders reuse the cached remainders highlighted in the latest divisor-cycle benchmarks instead of recomputing slow modulo checks.
|
||||
- PerfectNumbers.Core/ULongExtensions.cs:279: Swap this modulo check for the shared small-prime cycle filter once the divisor-cycle cache is mandatory, matching the PrimeTester improvements noted in the CPU sieve benchmarks.
|
||||
|
||||
Reference in new issue
Block a user