Use heuristic sieves for prime trial division
This commit is contained in:
1 parent
15c229665d
commit
1f7680a465
4 files changed
+235
-211
No files matched your search
@@ -15,195 +15,111 @@ public static class HeuristicPrimeSieves
|
|||||||
private static readonly ushort[] GroupBResiduesEnding7 = [17, 37, 47, 67, 97, 107, 127, 137, 157, 167, 187, 197];
|
private static readonly ushort[] GroupBResiduesEnding7 = [17, 37, 47, 67, 97, 107, 127, 137, 157, 167, 187, 197];
|
||||||
private static readonly ushort[] GroupBResiduesEnding9 = [19, 29, 59, 79, 89, 109, 139, 149, 169, 179, 199, 209];
|
private static readonly ushort[] GroupBResiduesEnding9 = [19, 29, 59, 79, 89, 109, 139, 149, 169, 179, 199, 209];
|
||||||
|
|
||||||
private static int[]? s_groupADivisors;
|
private static readonly int[] GroupADivisorsStorage;
|
||||||
private static ulong[]? s_groupADivisorSquares;
|
private static readonly ulong[] GroupADivisorSquaresStorage;
|
||||||
|
|
||||||
private static int[]? s_groupBDivisorsEnding1;
|
private static readonly int[] GroupBDivisorsEnding1Storage;
|
||||||
private static ulong[]? s_groupBDivisorSquaresEnding1;
|
private static readonly ulong[] GroupBDivisorSquaresEnding1Storage;
|
||||||
|
|
||||||
private static int[]? s_groupBDivisorsEnding3;
|
private static readonly int[] GroupBDivisorsEnding3Storage;
|
||||||
private static ulong[]? s_groupBDivisorSquaresEnding3;
|
private static readonly ulong[] GroupBDivisorSquaresEnding3Storage;
|
||||||
|
|
||||||
private static int[]? s_groupBDivisorsEnding7;
|
private static readonly int[] GroupBDivisorsEnding7Storage;
|
||||||
private static ulong[]? s_groupBDivisorSquaresEnding7;
|
private static readonly ulong[] GroupBDivisorSquaresEnding7Storage;
|
||||||
|
|
||||||
private static int[]? s_groupBDivisorsEnding9;
|
private static readonly int[] GroupBDivisorsEnding9Storage;
|
||||||
private static ulong[]? s_groupBDivisorSquaresEnding9;
|
private static readonly ulong[] GroupBDivisorSquaresEnding9Storage;
|
||||||
|
|
||||||
private static readonly object InitLock = new();
|
static HeuristicPrimeSieves()
|
||||||
|
|
||||||
public static ReadOnlySpan<int> GroupADivisors
|
|
||||||
{
|
{
|
||||||
get
|
Console.WriteLine("[HeuristicPrimeSieves] Initializing heuristic divisor sieves...");
|
||||||
{
|
|
||||||
EnsureGroupAInitialized();
|
(GroupADivisorsStorage, GroupADivisorSquaresStorage) = BuildGroupADivisors();
|
||||||
return s_groupADivisors!;
|
(GroupBDivisorsEnding1Storage, GroupBDivisorSquaresEnding1Storage) = BuildGroupBResidueSieve(GroupBResiduesEnding1);
|
||||||
}
|
(GroupBDivisorsEnding3Storage, GroupBDivisorSquaresEnding3Storage) = BuildGroupBResidueSieve(GroupBResiduesEnding3);
|
||||||
|
(GroupBDivisorsEnding7Storage, GroupBDivisorSquaresEnding7Storage) = BuildGroupBResidueSieve(GroupBResiduesEnding7);
|
||||||
|
(GroupBDivisorsEnding9Storage, GroupBDivisorSquaresEnding9Storage) = BuildGroupBResidueSieve(GroupBResiduesEnding9);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReadOnlySpan<ulong> GroupADivisorSquares
|
public static void EnsureInitialized()
|
||||||
{
|
{
|
||||||
get
|
// Intentionally left blank. Accessing this method forces the static constructor to run.
|
||||||
{
|
|
||||||
EnsureGroupAInitialized();
|
|
||||||
return s_groupADivisorSquares!;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReadOnlySpan<int> GroupBDivisorsEnding1
|
public static ReadOnlySpan<int> GroupADivisors => GroupADivisorsStorage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<ulong> GroupADivisorSquares => GroupADivisorSquaresStorage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<int> GroupBDivisorsEnding1 => GroupBDivisorsEnding1Storage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding1 => GroupBDivisorSquaresEnding1Storage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<int> GroupBDivisorsEnding3 => GroupBDivisorsEnding3Storage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding3 => GroupBDivisorSquaresEnding3Storage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<int> GroupBDivisorsEnding7 => GroupBDivisorsEnding7Storage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding7 => GroupBDivisorSquaresEnding7Storage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<int> GroupBDivisorsEnding9 => GroupBDivisorsEnding9Storage;
|
||||||
|
|
||||||
|
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding9 => GroupBDivisorSquaresEnding9Storage;
|
||||||
|
|
||||||
|
private static (int[] divisors, ulong[] squares) BuildGroupADivisors()
|
||||||
{
|
{
|
||||||
get
|
var divisors = new int[SieveLength];
|
||||||
|
var squares = new ulong[SieveLength];
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (; index < GroupAConstants.Length && index < divisors.Length; index++)
|
||||||
{
|
{
|
||||||
EnsureGroupBInitialized(ref s_groupBDivisorsEnding1, ref s_groupBDivisorSquaresEnding1, GroupBResiduesEnding1);
|
int value = GroupAConstants[index];
|
||||||
return s_groupBDivisorsEnding1!;
|
divisors[index] = value;
|
||||||
|
squares[index] = (ulong)value * (ulong)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int candidate = 23;
|
||||||
|
int incrementIndex = 0;
|
||||||
|
while (index < divisors.Length)
|
||||||
|
{
|
||||||
|
divisors[index] = candidate;
|
||||||
|
squares[index] = (ulong)candidate * (ulong)candidate;
|
||||||
|
|
||||||
|
candidate += GroupAIncrementPattern[incrementIndex];
|
||||||
|
incrementIndex ^= 1;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (divisors, squares);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding1
|
private static (int[] divisors, ulong[] squares) BuildGroupBResidueSieve(ReadOnlySpan<ushort> residues)
|
||||||
{
|
{
|
||||||
get
|
var divisors = new int[SieveLength];
|
||||||
{
|
var squares = new ulong[SieveLength];
|
||||||
EnsureGroupBInitialized(ref s_groupBDivisorsEnding1, ref s_groupBDivisorSquaresEnding1, GroupBResiduesEnding1);
|
|
||||||
return s_groupBDivisorSquaresEnding1!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlySpan<int> GroupBDivisorsEnding3
|
int index = 0;
|
||||||
{
|
ulong baseValue = 0UL;
|
||||||
get
|
|
||||||
{
|
|
||||||
EnsureGroupBInitialized(ref s_groupBDivisorsEnding3, ref s_groupBDivisorSquaresEnding3, GroupBResiduesEnding3);
|
|
||||||
return s_groupBDivisorsEnding3!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding3
|
while (index < divisors.Length)
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
EnsureGroupBInitialized(ref s_groupBDivisorsEnding3, ref s_groupBDivisorSquaresEnding3, GroupBResiduesEnding3);
|
for (int i = 0; i < residues.Length && index < divisors.Length; i++)
|
||||||
return s_groupBDivisorSquaresEnding3!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlySpan<int> GroupBDivisorsEnding7
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
EnsureGroupBInitialized(ref s_groupBDivisorsEnding7, ref s_groupBDivisorSquaresEnding7, GroupBResiduesEnding7);
|
|
||||||
return s_groupBDivisorsEnding7!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding7
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
EnsureGroupBInitialized(ref s_groupBDivisorsEnding7, ref s_groupBDivisorSquaresEnding7, GroupBResiduesEnding7);
|
|
||||||
return s_groupBDivisorSquaresEnding7!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlySpan<int> GroupBDivisorsEnding9
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
EnsureGroupBInitialized(ref s_groupBDivisorsEnding9, ref s_groupBDivisorSquaresEnding9, GroupBResiduesEnding9);
|
|
||||||
return s_groupBDivisorsEnding9!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding9
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
EnsureGroupBInitialized(ref s_groupBDivisorsEnding9, ref s_groupBDivisorSquaresEnding9, GroupBResiduesEnding9);
|
|
||||||
return s_groupBDivisorSquaresEnding9!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EnsureGroupAInitialized()
|
|
||||||
{
|
|
||||||
if (s_groupADivisors is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (InitLock)
|
|
||||||
{
|
|
||||||
if (s_groupADivisors is not null)
|
|
||||||
{
|
{
|
||||||
return;
|
ulong candidate = baseValue + residues[i];
|
||||||
}
|
if (candidate <= 13UL)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var divisors = new int[SieveLength];
|
divisors[index] = (int)candidate;
|
||||||
var squares = new ulong[SieveLength];
|
squares[index] = candidate * candidate;
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
for (; index < GroupAConstants.Length && index < divisors.Length; index++)
|
|
||||||
{
|
|
||||||
int value = GroupAConstants[index];
|
|
||||||
divisors[index] = value;
|
|
||||||
squares[index] = (ulong)value * (ulong)value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int candidate = 23;
|
|
||||||
int incrementIndex = 0;
|
|
||||||
while (index < divisors.Length)
|
|
||||||
{
|
|
||||||
divisors[index] = candidate;
|
|
||||||
squares[index] = (ulong)candidate * (ulong)candidate;
|
|
||||||
|
|
||||||
candidate += GroupAIncrementPattern[incrementIndex];
|
|
||||||
incrementIndex ^= 1;
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_groupADivisors = divisors;
|
baseValue += (ulong)Wheel210;
|
||||||
s_groupADivisorSquares = squares;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EnsureGroupBInitialized(ref int[]? divisorsField, ref ulong[]? squaresField, ReadOnlySpan<ushort> residues)
|
|
||||||
{
|
|
||||||
if (divisorsField is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (InitLock)
|
return (divisors, squares);
|
||||||
{
|
|
||||||
if (divisorsField is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var divisors = new int[SieveLength];
|
|
||||||
var squares = new ulong[SieveLength];
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
ulong baseValue = 0UL;
|
|
||||||
|
|
||||||
while (index < divisors.Length)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < residues.Length && index < divisors.Length; i++)
|
|
||||||
{
|
|
||||||
ulong candidate = baseValue + residues[i];
|
|
||||||
if (candidate <= 13UL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
divisors[index] = (int)candidate;
|
|
||||||
squares[index] = candidate * candidate;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
baseValue += (ulong)Wheel210;
|
|
||||||
}
|
|
||||||
|
|
||||||
divisorsField = divisors;
|
|
||||||
squaresField = squares;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,11 @@ namespace PerfectNumbers.Core;
|
|||||||
|
|
||||||
public sealed class PrimeTester(bool useInternal = false)
|
public sealed class PrimeTester(bool useInternal = false)
|
||||||
{
|
{
|
||||||
|
static PrimeTester()
|
||||||
|
{
|
||||||
|
HeuristicPrimeSieves.EnsureInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Don't use these tiny arrays. Hard-code the values / checks instead
|
// TODO: Don't use these tiny arrays. Hard-code the values / checks instead
|
||||||
private static readonly ulong[] GroupAConstantDivisors = [3UL, 7UL, 11UL, 13UL];
|
private static readonly ulong[] GroupAConstantDivisors = [3UL, 7UL, 11UL, 13UL];
|
||||||
private static readonly byte[] GroupAIncrementPattern = [20, 10];
|
private static readonly byte[] GroupAIncrementPattern = [20, 10];
|
||||||
@@ -197,20 +202,13 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
|
|
||||||
private static bool HeuristicTrialDivisionCpu(ulong n, ulong sqrtLimit, byte nMod10, bool includeGroupB)
|
private static bool HeuristicTrialDivisionCpu(ulong n, ulong sqrtLimit, byte nMod10, bool includeGroupB)
|
||||||
{
|
{
|
||||||
Span<HeuristicGroupBSequenceState> groupBBuffer = stackalloc HeuristicGroupBSequenceState[MaxGroupBSequences];
|
ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors;
|
||||||
var enumerator = new HeuristicDivisorEnumerator(sqrtLimit, nMod10, groupBBuffer);
|
for (int i = 0; i < groupADivisors.Length; i++)
|
||||||
|
|
||||||
while (enumerator.TryGetNext(out HeuristicDivisorCandidate candidate))
|
|
||||||
{
|
{
|
||||||
if (!includeGroupB && candidate.Group == HeuristicDivisorGroup.GroupB)
|
ulong divisor = (ulong)groupADivisors[i];
|
||||||
|
if (divisor > sqrtLimit)
|
||||||
{
|
{
|
||||||
return EvaluateWithOpenNumericFallback(n);
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
ulong divisor = candidate.Value;
|
|
||||||
if (divisor <= 1UL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n % divisor == 0UL)
|
if (n % divisor == 0UL)
|
||||||
@@ -219,20 +217,71 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return includeGroupB ? true : EvaluateWithOpenNumericFallback(n);
|
if (!includeGroupB)
|
||||||
|
{
|
||||||
|
return EvaluateWithOpenNumericFallback(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10);
|
||||||
|
if (endingOrder.IsEmpty)
|
||||||
|
{
|
||||||
|
return EvaluateWithOpenNumericFallback(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<int> indices = endingOrder.Length <= 8
|
||||||
|
? stackalloc int[endingOrder.Length]
|
||||||
|
: new int[endingOrder.Length];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ulong bestCandidate = ulong.MaxValue;
|
||||||
|
int bestEndingIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < endingOrder.Length; i++)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<int> divisors = GetGroupBDivisors(endingOrder[i]);
|
||||||
|
int index = indices[i];
|
||||||
|
if ((uint)index >= (uint)divisors.Length)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong candidate = (ulong)divisors[index];
|
||||||
|
if (candidate > sqrtLimit)
|
||||||
|
{
|
||||||
|
indices[i] = divisors.Length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestEndingIndex == -1 || candidate < bestCandidate)
|
||||||
|
{
|
||||||
|
bestCandidate = candidate;
|
||||||
|
bestEndingIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestEndingIndex == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n % bestCandidate == 0UL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
indices[bestEndingIndex]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10)
|
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10)
|
||||||
{
|
{
|
||||||
Span<HeuristicGroupBSequenceState> groupBBuffer = stackalloc HeuristicGroupBSequenceState[MaxGroupBSequences];
|
|
||||||
var enumerator = new HeuristicDivisorEnumerator(sqrtLimit, nMod10, groupBBuffer);
|
|
||||||
|
|
||||||
int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize);
|
int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize);
|
||||||
var candidatePool = ArrayPool<HeuristicDivisorCandidate>.Shared;
|
|
||||||
var divisorPool = ThreadStaticPools.UlongPool;
|
var divisorPool = ThreadStaticPools.UlongPool;
|
||||||
var hitPool = ThreadStaticPools.BytePool;
|
var hitPool = ThreadStaticPools.BytePool;
|
||||||
|
|
||||||
HeuristicDivisorCandidate[]? candidateArray = null;
|
|
||||||
ulong[]? divisorArray = null;
|
ulong[]? divisorArray = null;
|
||||||
byte[]? hitFlags = null;
|
byte[]? hitFlags = null;
|
||||||
|
|
||||||
@@ -243,7 +292,6 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
candidateArray = candidatePool.Rent(batchCapacity);
|
|
||||||
divisorArray = divisorPool.Rent(batchCapacity);
|
divisorArray = divisorPool.Rent(batchCapacity);
|
||||||
hitFlags = hitPool.Rent(batchCapacity);
|
hitFlags = hitPool.Rent(batchCapacity);
|
||||||
|
|
||||||
@@ -259,6 +307,11 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
|
|
||||||
bool ProcessBatch(int length)
|
bool ProcessBatch(int length)
|
||||||
{
|
{
|
||||||
|
if (length <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
scratch.Input.View.CopyFromCPU(ref divisorArray![0], length);
|
scratch.Input.View.CopyFromCPU(ref divisorArray![0], length);
|
||||||
state.HeuristicTrialDivisionKernel(length, scratch.Input.View, n, scratch.Output.View);
|
state.HeuristicTrialDivisionKernel(length, scratch.Input.View, n, scratch.Output.View);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
@@ -271,7 +324,7 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong divisor = candidateArray![i].Value;
|
ulong divisor = divisorArray![i];
|
||||||
if (divisor > 1UL && n % divisor == 0UL)
|
if (divisor > 1UL && n % divisor == 0UL)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@@ -281,36 +334,87 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (enumerator.TryGetNext(out HeuristicDivisorCandidate candidate))
|
bool QueueDivisor(ulong divisor)
|
||||||
{
|
{
|
||||||
ulong divisor = candidate.Value;
|
divisorArray![count] = divisor;
|
||||||
if (divisor <= 1UL)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
candidateArray[count] = candidate;
|
|
||||||
divisorArray[count] = divisor;
|
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
if (count == batchCapacity)
|
if (count == batchCapacity)
|
||||||
{
|
{
|
||||||
if (ProcessBatch(count))
|
if (ProcessBatch(count))
|
||||||
{
|
{
|
||||||
compositeDetected = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
count = 0;
|
count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors;
|
||||||
|
for (int i = 0; i < groupADivisors.Length && !compositeDetected; i++)
|
||||||
|
{
|
||||||
|
ulong divisor = (ulong)groupADivisors[i];
|
||||||
|
if (divisor > sqrtLimit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
compositeDetected = QueueDivisor(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!compositeDetected)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10);
|
||||||
|
if (!endingOrder.IsEmpty)
|
||||||
|
{
|
||||||
|
Span<int> indices = endingOrder.Length <= 8
|
||||||
|
? stackalloc int[endingOrder.Length]
|
||||||
|
: new int[endingOrder.Length];
|
||||||
|
|
||||||
|
while (!compositeDetected)
|
||||||
|
{
|
||||||
|
ulong bestCandidate = ulong.MaxValue;
|
||||||
|
int bestEndingIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < endingOrder.Length; i++)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<int> divisors = GetGroupBDivisors(endingOrder[i]);
|
||||||
|
int index = indices[i];
|
||||||
|
if ((uint)index >= (uint)divisors.Length)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong candidate = (ulong)divisors[index];
|
||||||
|
if (candidate > sqrtLimit)
|
||||||
|
{
|
||||||
|
indices[i] = divisors.Length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestEndingIndex == -1 || candidate < bestCandidate)
|
||||||
|
{
|
||||||
|
bestCandidate = candidate;
|
||||||
|
bestEndingIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestEndingIndex == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
compositeDetected = QueueDivisor(bestCandidate);
|
||||||
|
indices[bestEndingIndex]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compositeDetected && count > 0)
|
if (!compositeDetected && count > 0)
|
||||||
{
|
{
|
||||||
if (ProcessBatch(count))
|
compositeDetected = ProcessBatch(count);
|
||||||
{
|
|
||||||
compositeDetected = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -331,11 +435,6 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
divisorPool.Return(divisorArray);
|
divisorPool.Return(divisorArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candidateArray is not null)
|
|
||||||
{
|
|
||||||
candidatePool.Return(candidateArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
gpu.Dispose();
|
gpu.Dispose();
|
||||||
limiter.Dispose();
|
limiter.Dispose();
|
||||||
}
|
}
|
||||||
@@ -380,6 +479,15 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
_ => ReadOnlySpan<byte>.Empty,
|
_ => ReadOnlySpan<byte>.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static ReadOnlySpan<int> GetGroupBDivisors(byte ending) => ending switch
|
||||||
|
{
|
||||||
|
1 => HeuristicPrimeSieves.GroupBDivisorsEnding1,
|
||||||
|
3 => HeuristicPrimeSieves.GroupBDivisorsEnding3,
|
||||||
|
7 => HeuristicPrimeSieves.GroupBDivisorsEnding7,
|
||||||
|
9 => HeuristicPrimeSieves.GroupBDivisorsEnding9,
|
||||||
|
_ => ReadOnlySpan<int>.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
private static ReadOnlySpan<ushort> GetWheel210ResiduesForEnding(byte ending) => ending switch
|
private static ReadOnlySpan<ushort> GetWheel210ResiduesForEnding(byte ending) => ending switch
|
||||||
{
|
{
|
||||||
1 => Wheel210ResiduesEnding1,
|
1 => Wheel210ResiduesEnding1,
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels,
|
|||||||
1. [done] **Snapshot current implementations** – preserve `IsPrimeGpu` and `IsPrimeInternal` as references for regression comparisons and migration checkpoints (see Section 2).
|
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.
|
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.
|
||||||
3. [done] **Design shared heuristic scaffolding**
|
3. [done] **Design shared heuristic scaffolding**
|
||||||
* [done] Introduced `HeuristicDivisorEnumerator` and stackalloc-backed Group B sequence states so CPU/GPU heuristics share the Group A/B wheel metadata and candidate ordering.
|
* [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`; PrimeTester 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 `HeuristicIsPrimeCpu`; PrimeTester 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.
|
* [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**
|
4. **Share residue and wheel state with by-divisor sessions**
|
||||||
@@ -127,11 +127,11 @@ The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels,
|
|||||||
* [done] Extend the heuristic scaffolding so it returns `MontgomeryDivisorData` descriptors and cycle-length hints for each candidate divisor via `HeuristicDivisorPreparation` and `PrepareHeuristicDivisor`.
|
* [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.
|
* Wire these outputs into the existing cycle calculators to avoid duplicate cache lookups inside the CPU/GPU divisor scans.
|
||||||
6. [done] **Implement `HeuristicIsPrimeCpu`**
|
6. [done] **Implement `HeuristicIsPrimeCpu`**
|
||||||
* Baseline Group A/B enumeration now executes inside `HeuristicIsPrimeCpu` via `HeuristicDivisorEnumerator`, computing `⌊√n⌋` locally and short-circuiting on the first divisor.
|
* Baseline Group A/B enumeration now executes inside `HeuristicIsPrimeCpu` 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] 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.
|
* [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 `HeuristicIsPrimeGpu`**
|
||||||
* [done] Mirror CPU structure while batching divisor checks on the accelerator via `HeuristicTrialDivisionGpu`, reusing caller-provided `sqrtLimit`/`nMod10` inputs and falling back to CPU when GPUs are disabled.
|
* [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] 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.
|
* [done] Kept GPU divisibility in standard modular arithmetic while reserving Montgomery transforms for CPU confirmation logic.
|
||||||
* [done] When the temporary Group B gate is disabled, delegate GPU calls to the CPU fallback so Group A coverage remains in place while the accelerator path stays available for future re-enablement.
|
* [done] When the temporary Group B gate is disabled, delegate GPU calls to the CPU fallback so Group A coverage remains in place while the accelerator path stays available for future re-enablement.
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ The by-divisor executors (`MersenneNumberDivisorByDivisorCpuTester` and `Mersenn
|
|||||||
|
|
||||||
* **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`. `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.
|
||||||
* **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.
|
* **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** – The wheel 30/210 sequences emitted by `PrimeTester.CreateHeuristicDivisorEnumerator` now drive both the CPU and GPU by-divisor testers, keeping the divisor cache and kernel inputs in lockstep without bespoke residue loops.
|
* **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** – `PrimeTester.CreateHeuristicDivisorEnumerator` now feeds `MersenneNumberDivisorByDivisorCpuTester.CheckDivisors` and `MersenneNumberDivisorByDivisorGpuTester.CheckDivisors`, replacing bespoke residue loops while supplying candidates directly to Montgomery preparation and GPU kernels.
|
* **Divisor streaming API** – `PrimeTester.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 through the same enumerator, packs them into batches controlled by `PrimeTester.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** – `HeuristicIsPrimeGpu` streams Group A/B candidates from `HeuristicPrimeSieves`, packs them into batches controlled by `PrimeTester.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.
|
||||||
* **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. `PrimeTester.PrepareHeuristicDivisor` together with `PrimeTester.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.
|
* **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. `PrimeTester.PrepareHeuristicDivisor` together with `PrimeTester.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.
|
* **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.
|
||||||
@@ -97,8 +97,8 @@ The following summaries cover the first 500,000 odd composite numbers after remo
|
|||||||
|
|
||||||
### Heuristic cheat sheet
|
### Heuristic cheat sheet
|
||||||
|
|
||||||
* Group A: test `3, 7, 11, 13` and every `d ≡ 3 (mod 10)` up to `⌊√n⌋` (wheel 30).
|
* Group A: test `3, 7, 11, 13` and every `d ≡ 3 (mod 10)` up to `⌊√n⌋` from the precomputed wheel-30 sieve.
|
||||||
* Group B: test only the allowed endings determined by `n mod 10`, scanning in ascending order (wheel 210) and applying the per-class priority order: `n ≡ 1` → `9` then `1`; `n ≡ 3` → `9` then `7`; `n ≡ 7` → `7` then `1`; `n ≡ 9` → `9`, `7`, `1`.
|
* Group B: test only the allowed endings determined by `n mod 10`, scanning in ascending order from the wheel-210 sieves and applying the per-class priority order: `n ≡ 1` → `9` then `1`; `n ≡ 3` → `9` then `7`; `n ≡ 7` → `7` then `1`; `n ≡ 9` → `9`, `7`, `1`.
|
||||||
* Because `min(B)` is overwhelmingly smaller than `A_max(n)` for the “no Group A divisor” cohort, always start Group B from its smallest candidates instead of resuming above `A_max(n)`.
|
* Because `min(B)` is overwhelmingly smaller than `A_max(n)` for the “no Group A divisor” cohort, always start Group B from its smallest candidates instead of resuming above `A_max(n)`.
|
||||||
|
|
||||||
## Benchmark Reporting
|
## Benchmark Reporting
|
||||||
|
|||||||
Reference in new issue
Block a user