Interleave heuristic prime tester batches across groups

This commit is contained in:
Marek Lemańczyk committed 2025-10-26 04:01:56 +01:00
1 parent 6e632ba03a
commit a747f29ac2
1 file changed
+235 -88
+235 -88
View File
@@ -203,22 +203,44 @@ 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)
{ {
ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors; ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors;
for (int i = 0; i < groupADivisors.Length; i++) int interleaveBatchSize = Math.Max(1, HeuristicDivisorInterleaveBatchSize);
{ int groupAIndex = 0;
ulong divisor = (ulong)groupADivisors[i];
if (divisor > sqrtLimit)
{
break;
}
if (n % divisor == 0UL)
{
return false;
}
}
if (!includeGroupB) if (!includeGroupB)
{ {
while (groupAIndex < groupADivisors.Length)
{
int processed = 0;
while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length)
{
ulong divisor = (ulong)groupADivisors[groupAIndex];
if (divisor > sqrtLimit)
{
groupAIndex = groupADivisors.Length;
break;
}
if (n % divisor == 0UL)
{
return false;
}
groupAIndex++;
processed++;
}
if (groupAIndex >= groupADivisors.Length)
{
break;
}
if ((ulong)groupADivisors[groupAIndex] > sqrtLimit)
{
groupAIndex = groupADivisors.Length;
break;
}
}
return EvaluateWithOpenNumericFallback(n); return EvaluateWithOpenNumericFallback(n);
} }
@@ -232,53 +254,82 @@ public sealed class PrimeTester(bool useInternal = false)
? stackalloc int[endingOrder.Length] ? stackalloc int[endingOrder.Length]
: new int[endingOrder.Length]; : new int[endingOrder.Length];
while (true) bool groupAHasMore = groupAIndex < groupADivisors.Length && (ulong)groupADivisors[groupAIndex] <= sqrtLimit;
bool groupBHasMore = HasGroupBCandidates(endingOrder, indices, sqrtLimit);
while (groupAHasMore || groupBHasMore)
{ {
ulong bestCandidate = ulong.MaxValue; if (groupAHasMore)
int bestEndingIndex = -1;
for (int i = 0; i < endingOrder.Length; i++)
{ {
ReadOnlySpan<int> divisors = GetGroupBDivisors(endingOrder[i]); int processed = 0;
int index = indices[i]; while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length)
if ((uint)index >= (uint)divisors.Length)
{ {
continue; ulong divisor = (ulong)groupADivisors[groupAIndex];
if (divisor > sqrtLimit)
{
groupAIndex = groupADivisors.Length;
groupAHasMore = false;
break;
}
if (n % divisor == 0UL)
{
return false;
}
groupAIndex++;
processed++;
} }
ulong candidate = (ulong)divisors[index]; if (groupAIndex >= groupADivisors.Length)
if (candidate > sqrtLimit)
{ {
indices[i] = divisors.Length; groupAHasMore = false;
continue;
} }
else
if (bestEndingIndex == -1 || candidate < bestCandidate)
{ {
bestCandidate = candidate; groupAHasMore = (ulong)groupADivisors[groupAIndex] <= sqrtLimit;
bestEndingIndex = i; if (!groupAHasMore)
{
groupAIndex = groupADivisors.Length;
}
} }
} }
if (bestEndingIndex == -1) if (groupBHasMore)
{ {
break; int processed = 0;
} while (processed < interleaveBatchSize)
{
if (!TrySelectNextGroupBDivisor(endingOrder, indices, sqrtLimit, out ulong divisor))
{
groupBHasMore = false;
break;
}
if (n % bestCandidate == 0UL) if (n % divisor == 0UL)
{ {
return false; return false;
} }
indices[bestEndingIndex]++; processed++;
}
if (groupBHasMore)
{
groupBHasMore = HasGroupBCandidates(endingOrder, indices, sqrtLimit);
}
}
} }
return true; return true;
} }
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10) private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10)
{ {
int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize); int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize);
int interleaveBatchSize = Math.Max(1, HeuristicDivisorInterleaveBatchSize);
var divisorPool = ThreadStaticPools.UlongPool; var divisorPool = ThreadStaticPools.UlongPool;
var hitPool = ThreadStaticPools.BytePool; var hitPool = ThreadStaticPools.BytePool;
@@ -353,61 +404,87 @@ public sealed class PrimeTester(bool useInternal = false)
} }
ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors; ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors;
for (int i = 0; i < groupADivisors.Length && !compositeDetected; i++) 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))
{ {
ulong divisor = (ulong)groupADivisors[i]; if (groupAHasMore)
if (divisor > sqrtLimit)
{ {
break; int processed = 0;
} while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length)
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; ulong divisor = (ulong)groupADivisors[groupAIndex];
int bestEndingIndex = -1; if (divisor > sqrtLimit)
for (int i = 0; i < endingOrder.Length; i++)
{ {
ReadOnlySpan<int> divisors = GetGroupBDivisors(endingOrder[i]); groupAIndex = groupADivisors.Length;
int index = indices[i]; groupAHasMore = false;
if ((uint)index >= (uint)divisors.Length) break;
{
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) compositeDetected = QueueDivisor(divisor);
if (compositeDetected)
{ {
break; break;
} }
compositeDetected = QueueDivisor(bestCandidate); groupAIndex++;
indices[bestEndingIndex]++; 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);
} }
} }
} }
@@ -425,16 +502,16 @@ public sealed class PrimeTester(bool useInternal = false)
} }
finally finally
{ {
if (hitFlags is not null)
{
hitPool.Return(hitFlags);
}
if (divisorArray is not null) if (divisorArray is not null)
{ {
divisorPool.Return(divisorArray); divisorPool.Return(divisorArray);
} }
if (hitFlags is not null)
{
hitPool.Return(hitFlags);
}
gpu.Dispose(); gpu.Dispose();
limiter.Dispose(); limiter.Dispose();
} }
@@ -442,6 +519,8 @@ public sealed class PrimeTester(bool useInternal = false)
return compositeDetected; return compositeDetected;
} }
private static bool EvaluateWithOpenNumericFallback(ulong n) private static bool EvaluateWithOpenNumericFallback(ulong n)
{ {
return Prime.Numbers.IsPrime(n); return Prime.Numbers.IsPrime(n);
@@ -479,6 +558,72 @@ public sealed class PrimeTester(bool useInternal = false)
_ => ReadOnlySpan<byte>.Empty, _ => ReadOnlySpan<byte>.Empty,
}; };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TrySelectNextGroupBDivisor(ReadOnlySpan<byte> endingOrder, Span<int> indices, ulong sqrtLimit, out ulong divisor)
{
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)
{
divisor = 0UL;
return false;
}
divisor = bestCandidate;
indices[bestEndingIndex]++;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool HasGroupBCandidates(ReadOnlySpan<byte> endingOrder, Span<int> indices, ulong sqrtLimit)
{
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;
}
return true;
}
return false;
}
private static ReadOnlySpan<int> GetGroupBDivisors(byte ending) => ending switch private static ReadOnlySpan<int> GetGroupBDivisors(byte ending) => ending switch
{ {
1 => HeuristicPrimeSieves.GroupBDivisorsEnding1, 1 => HeuristicPrimeSieves.GroupBDivisorsEnding1,
@@ -906,6 +1051,8 @@ public sealed class PrimeTester(bool useInternal = false)
public static int HeuristicGpuDivisorBatchSize { get; set; } = 4_096; public static int HeuristicGpuDivisorBatchSize { get; set; } = 4_096;
public static int HeuristicDivisorInterleaveBatchSize { get; set; } = 64;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsPrimeBatchGpu(ReadOnlySpan<ulong> values, Span<byte> results) public static void IsPrimeBatchGpu(ReadOnlySpan<ulong> values, Span<byte> results)