Compare commits

...
Author SHA1 Message Date
Marek Lemańczyk 0466d98434 Hoist by-divisor invariants and split mod-8 updates 2025-10-27 16:48:02 +01:00
7 changed files with 116 additions and 126 deletions

No files matched your search

@@ -7,10 +7,7 @@ namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
{ {
private readonly object _sync = new();
private ulong _divisorLimit; private ulong _divisorLimit;
private ulong _lastStatusDivisor;
private int _batchSize = 1_024; private int _batchSize = 1_024;
[ThreadStatic] [ThreadStatic]
@@ -32,7 +29,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public void ConfigureFromMaxPrime(ulong maxPrime) public void ConfigureFromMaxPrime(ulong maxPrime)
{ {
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
_lastStatusDivisor = 0UL;
} }
public ulong DivisorLimit public ulong DivisorLimit
@@ -60,22 +56,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
// return true; // return true;
// } // }
ulong processedCount;
bool processedAll; bool processedAll;
bool composite = CheckDivisors( bool composite = CheckDivisors(
prime, prime,
allowedMax, allowedMax,
out processedAll, out processedAll);
out processedCount);
if (processedCount > 0UL)
{
lock (_sync)
{
UpdateStatusUnsafe(processedCount);
}
}
if (composite) if (composite)
{ {
@@ -111,10 +97,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private bool CheckDivisors( private bool CheckDivisors(
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
out bool processedAll, out bool processedAll)
out ulong processedCount)
{ {
processedCount = 0UL;
processedAll = false; processedAll = false;
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs. // The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
@@ -170,24 +154,20 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
// Keep the divisibility filters aligned with the divisor-cycle generator so the // Keep the divisibility filters aligned with the divisor-cycle generator so the
// CPU path never requests cycles that were skipped during cache creation. // CPU path never requests cycles that were skipped during cache creation.
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
bool lastIsSeven = lastDigit == LastDigit.Seven; ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
while (divisor.CompareTo(limit) <= 0) while (divisor.CompareTo(limit) <= 0)
{ {
ulong candidate = divisor.Low; ulong candidate = divisor.Low;
processedCount++;
ushort decimalMask = lastIsSeven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise; bool admissible = ((decimalMask >> remainder10) & 1) != 0
bool admissible = ((decimalMask >> remainder10) & 1) != 0;
if (admissible
&& (remainder8 == 1 || remainder8 == 7) && (remainder8 == 1 || remainder8 == 7)
&& remainder3 != 0 && remainder3 != 0
&& remainder5 != 0 && remainder5 != 0
&& remainder7 != 0 && remainder7 != 0
&& remainder11 != 0) && remainder11 != 0;
if (admissible)
{ {
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
ulong divisorCycle; ulong divisorCycle;
@@ -234,7 +214,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
divisor.Add(step); divisor.Add(step);
remainder10 = AddMod(remainder10, step10, (byte)10); remainder10 = AddMod(remainder10, step10, (byte)10);
remainder8 = AddMod(remainder8, step8, (byte)8); remainder8 = AddMod8(remainder8, step8);
remainder5 = AddMod(remainder5, step5, (byte)5); remainder5 = AddMod(remainder5, step5, (byte)5);
remainder3 = AddMod(remainder3, step3, (byte)3); remainder3 = AddMod(remainder3, step3, (byte)3);
remainder7 = AddMod(remainder7, step7, (byte)7); remainder7 = AddMod(remainder7, step7, (byte)7);
@@ -251,9 +231,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return residue == 1UL ? (byte)1 : (byte)0; return residue == 1UL ? (byte)1 : (byte)0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod(byte value, byte delta, byte modulus) private static byte AddMod(byte value, byte delta, byte modulus)
{ {
int sum = value + delta; int sum = value + delta;
if (sum >= modulus) if (sum >= modulus)
{ {
sum -= modulus; sum -= modulus;
@@ -262,24 +244,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return (byte)sum; return (byte)sum;
} }
private void UpdateStatusUnsafe(ulong processedCount) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod8(byte value, byte delta)
{ {
if (processedCount == 0UL) return (byte)((value + delta) & 7);
{
return;
}
ulong interval = PerfectNumberConstants.ConsoleInterval;
if (interval == 0UL)
{
_lastStatusDivisor = 0UL;
return;
}
ulong total = _lastStatusDivisor + processedCount;
// TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
// status benchmarks so we avoid `%` in this hot loop while still wrapping progress correctly.
_lastStatusDivisor = total % interval;
} }
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime) private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
+7 -2
View File
@@ -7,6 +7,12 @@ internal static class DivisorGenerator
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9); private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9); private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort GetDecimalMask(LastDigit lastDigit)
{
return lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsValidDivisor( public static bool IsValidDivisor(
byte remainder10, byte remainder10,
@@ -15,9 +21,8 @@ internal static class DivisorGenerator
byte remainder5, byte remainder5,
byte remainder7, byte remainder7,
byte remainder11, byte remainder11,
LastDigit lastDigit) ushort decimalMask)
{ {
ushort decimalMask = lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
if (((decimalMask >> remainder10) & 1) == 0) if (((decimalMask >> remainder10) & 1) == 0)
{ {
return false; return false;
@@ -36,28 +36,30 @@ internal static class DivisorByDivisorKernels
byte firstHitValue; byte firstHitValue;
ulong previousExponent = firstExponent; ulong previousExponent = firstExponent;
ulong cycleRemainder = 0UL; ulong cycleRemainder = 0UL;
if (cycleLength == 0UL) bool hasCycle = cycleLength != 0UL;
{ if (hasCycle)
firstHitValue = firstUnity ? (byte)1 : (byte)0;
}
else
{ {
cycleRemainder = firstExponent % cycleLength; cycleRemainder = firstExponent % cycleLength;
firstHitValue = cycleRemainder == 0UL && firstUnity ? (byte)1 : (byte)0; firstHitValue = cycleRemainder == 0UL && firstUnity ? (byte)1 : (byte)0;
} }
else
{
firstHitValue = firstUnity ? (byte)1 : (byte)0;
}
hits[offset] = firstHitValue; hits[offset] = firstHitValue;
byte anyHit = firstHitValue; byte anyHit = firstHitValue;
for (int i = 1; i < count; i++) int endIndex = offset + count;
if (hasCycle)
{ {
ulong exponent = exponents[offset + i]; GpuUInt128 remainderValue = new(cycleRemainder);
ulong delta = exponent - previousExponent; for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++)
previousExponent = exponent;
if (cycleLength != 0UL)
{ {
var remainderValue = new GpuUInt128(cycleRemainder); ulong exponent = exponents[exponentIndex];
ulong delta = exponent - previousExponent;
previousExponent = exponent;
remainderValue.AddMod(delta, cycleLength); remainderValue.AddMod(delta, cycleLength);
cycleRemainder = remainderValue.Low; cycleRemainder = remainderValue.Low;
@@ -67,13 +69,17 @@ internal static class DivisorByDivisorKernels
hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0; hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0;
} }
hits[offset + i] = hit; hits[exponentIndex] = hit;
anyHit |= hit; anyHit |= hit;
} }
else }
else
{
for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++)
{ {
ulong exponent = exponents[exponentIndex];
byte hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0; byte hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0;
hits[offset + i] = hit; hits[exponentIndex] = hit;
anyHit |= hit; anyHit |= hit;
} }
} }
@@ -192,7 +192,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
processedCount = 0UL; processedCount = 0UL;
lastProcessed = 0UL; lastProcessed = 0UL;
int chunkCapacity = Math.Max(1, batchCapacity); // EvenPerfectBitScanner always configures non-empty GPU divisor buffers, so the defensive guard stays
// commented out. Tests and benchmarks that rely on zero-capacity buffers must be updated instead of re-enabling
// this branch.
// if (batchCapacity <= 0)
// {
// throw new InvalidOperationException("Divisor buffers must be non-empty.");
// }
int chunkCountBaseline = batchCapacity;
UInt128 twoP128 = (UInt128)prime << 1; UInt128 twoP128 = (UInt128)prime << 1;
UInt128 allowedMax128 = allowedMax; UInt128 allowedMax128 = allowedMax;
@@ -209,7 +217,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128; ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
ulong currentK = 1UL; ulong remainingCount = maxK;
byte step10 = (byte)(twoP128 % 10UL); byte step10 = (byte)(twoP128 % 10UL);
byte step8 = (byte)(twoP128 % 8UL); byte step8 = (byte)(twoP128 % 8UL);
@@ -226,6 +234,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
byte remainder7 = (byte)((ulong)(currentDivisor128 % 7UL)); byte remainder7 = (byte)((ulong)(currentDivisor128 % 7UL));
byte remainder11 = (byte)((ulong)(currentDivisor128 % 11UL)); byte remainder11 = (byte)((ulong)(currentDivisor128 % 11UL));
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
Span<ulong> filteredStorage = filteredDivisors.AsSpan(); Span<ulong> filteredStorage = filteredDivisors.AsSpan();
Span<ulong> divisorStorage = divisors.AsSpan(); Span<ulong> divisorStorage = divisors.AsSpan();
@@ -234,19 +243,32 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
Span<int> offsetStorage = offsets.AsSpan(); Span<int> offsetStorage = offsets.AsSpan();
Span<int> countStorage = counts.AsSpan(); Span<int> countStorage = counts.AsSpan();
Span<ulong> cycleStorage = cycles.AsSpan(); Span<ulong> cycleStorage = cycles.AsSpan();
while (currentK <= maxK && !composite) Span<ulong> divisorSpan = divisorStorage;
{ Span<ulong> exponentSpan = exponentStorage;
int chunkCount = Math.Min(chunkCapacity, batchCapacity); Span<GpuDivisorPartialData> divisorDataSpan = divisorDataStorage;
ulong remainingK = maxK - currentK + 1UL; Span<int> offsetSpan = offsetStorage;
if ((ulong)chunkCount > remainingK) Span<int> countSpan = countStorage;
{ Span<ulong> cycleSpan = cycleStorage;
chunkCount = (int)remainingK; ref GpuDivisorPartialData divisorDataRef = ref MemoryMarshal.GetReference(divisorDataSpan);
} ref int offsetRef = ref MemoryMarshal.GetReference(offsetSpan);
ref int countRef = ref MemoryMarshal.GetReference(countSpan);
ref ulong cycleRef = ref MemoryMarshal.GetReference(cycleSpan);
ref ulong exponentRef = ref MemoryMarshal.GetReference(exponentSpan);
if (chunkCount <= 0) ArrayView1D<GpuDivisorPartialData, Stride1D.Dense> divisorDataView = divisorDataBuffer.View;
ArrayView1D<int, Stride1D.Dense> offsetView = offsetBuffer.View;
ArrayView1D<int, Stride1D.Dense> countView = countBuffer.View;
ArrayView1D<ulong, Stride1D.Dense> cycleView = cycleBuffer.View;
ArrayView1D<ulong, Stride1D.Dense> exponentViewDevice = exponentBuffer.View;
ArrayView1D<byte, Stride1D.Dense> hitsView = hitsBuffer.View;
ArrayView1D<int, Stride1D.Dense> hitIndexView = hitIndexBuffer.View;
while (remainingCount > 0UL && !composite)
{
int chunkCount = chunkCountBaseline;
if ((ulong)chunkCount > remainingCount)
{ {
processedAll = true; chunkCount = (int)remainingCount;
break;
} }
int filteredCount = 0; int filteredCount = 0;
@@ -263,14 +285,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
{ {
ulong candidate = (ulong)nextDivisor128; ulong candidate = (ulong)nextDivisor128;
if (DivisorGenerator.IsValidDivisor(localRemainder10, localRemainder8, localRemainder3, localRemainder5, localRemainder7, localRemainder11, lastDigit)) if (DivisorGenerator.IsValidDivisor(localRemainder10, localRemainder8, localRemainder3, localRemainder5, localRemainder7, localRemainder11, decimalMask))
{ {
filteredStorage[filteredCount++] = candidate; filteredStorage[filteredCount++] = candidate;
} }
nextDivisor128 += twoP128; nextDivisor128 += twoP128;
localRemainder10 = AddMod(localRemainder10, step10, 10); localRemainder10 = AddMod(localRemainder10, step10, 10);
localRemainder8 = AddMod(localRemainder8, step8, 8); localRemainder8 = AddMod8(localRemainder8, step8);
localRemainder5 = AddMod(localRemainder5, step5, 5); localRemainder5 = AddMod(localRemainder5, step5, 5);
localRemainder3 = AddMod(localRemainder3, step3, 3); localRemainder3 = AddMod(localRemainder3, step3, 3);
localRemainder7 = AddMod(localRemainder7, step7, 7); localRemainder7 = AddMod(localRemainder7, step7, 7);
@@ -289,28 +311,17 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
UInt128 lastDivisor128 = nextDivisor128 - twoP128; UInt128 lastDivisor128 = nextDivisor128 - twoP128;
lastProcessed = (ulong)lastDivisor128; lastProcessed = (ulong)lastDivisor128;
currentDivisor128 = nextDivisor128; currentDivisor128 = nextDivisor128;
remainingCount -= (ulong)chunkCount;
currentK += (ulong)chunkCount;
processedAll = currentK > maxK;
if (filteredCount == 0) if (filteredCount == 0)
{ {
continue; continue;
} }
Span<ulong> filteredDivisorsSpan = filteredStorage[..filteredCount];
Span<ulong> divisorSpan = divisorStorage;
Span<ulong> exponentSpan = exponentStorage;
Span<GpuDivisorPartialData> divisorDataSpan = divisorDataStorage;
Span<int> offsetSpan = offsetStorage;
Span<int> countSpan = countStorage;
Span<ulong> cycleSpan = cycleStorage;
int admissibleCount = 0; int admissibleCount = 0;
int exponentIndex = 0;
for (int i = 0; i < filteredCount; i++) for (int i = 0; i < filteredCount; i++)
{ {
ulong divisorValue = filteredDivisorsSpan[i]; ulong divisorValue = filteredStorage[i];
MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue); MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue);
ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData); ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData);
if (divisorCycle != prime) if (divisorCycle != prime)
@@ -319,14 +330,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
divisorSpan[admissibleCount] = divisorValue; divisorSpan[admissibleCount] = divisorValue;
exponentSpan[exponentIndex] = prime; exponentSpan[admissibleCount] = prime;
divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue); divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue);
offsetSpan[admissibleCount] = exponentIndex; offsetSpan[admissibleCount] = admissibleCount;
countSpan[admissibleCount] = 1; countSpan[admissibleCount] = 1;
cycleSpan[admissibleCount] = divisorCycle; cycleSpan[admissibleCount] = divisorCycle;
admissibleCount++; admissibleCount++;
exponentIndex++;
} }
if (admissibleCount == 0) if (admissibleCount == 0)
@@ -334,24 +344,16 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
continue; continue;
} }
ArrayView1D<GpuDivisorPartialData, Stride1D.Dense> divisorView = divisorDataBuffer.View.SubView(0, admissibleCount); divisorDataView.CopyFromCPU(ref divisorDataRef, admissibleCount);
ArrayView1D<int, Stride1D.Dense> offsetView = offsetBuffer.View.SubView(0, admissibleCount); offsetView.CopyFromCPU(ref offsetRef, admissibleCount);
ArrayView1D<int, Stride1D.Dense> countView = countBuffer.View.SubView(0, admissibleCount); countView.CopyFromCPU(ref countRef, admissibleCount);
ArrayView1D<ulong, Stride1D.Dense> cycleView = cycleBuffer.View.SubView(0, admissibleCount); cycleView.CopyFromCPU(ref cycleRef, admissibleCount);
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentBuffer.View.SubView(0, exponentIndex); exponentViewDevice.CopyFromCPU(ref exponentRef, admissibleCount);
ArrayView1D<byte, Stride1D.Dense> hitsView = hitsBuffer.View.SubView(0, exponentIndex);
ArrayView1D<int, Stride1D.Dense> hitIndexView = hitIndexBuffer.View.SubView(0, 1);
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount);
offsetView.CopyFromCPU(ref MemoryMarshal.GetReference(offsetSpan), admissibleCount);
countView.CopyFromCPU(ref MemoryMarshal.GetReference(countSpan), admissibleCount);
cycleView.CopyFromCPU(ref MemoryMarshal.GetReference(cycleSpan), admissibleCount);
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), exponentIndex);
int sentinel = int.MaxValue; int sentinel = int.MaxValue;
hitIndexView.CopyFromCPU(ref sentinel, 1); hitIndexView.CopyFromCPU(ref sentinel, 1);
kernel(admissibleCount, divisorView, offsetView, countView, exponentView, cycleView, hitsView, hitIndexView); kernel(admissibleCount, divisorDataView, offsetView, countView, exponentViewDevice, cycleView, hitsView, hitIndexView);
int firstHit = sentinel; int firstHit = sentinel;
hitIndexView.CopyToCPU(ref firstHit, 1); hitIndexView.CopyToCPU(ref firstHit, 1);
@@ -362,6 +364,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex]; lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex];
} }
processedAll = remainingCount == 0UL;
coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128); coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128);
return composite; return composite;
} }
@@ -382,19 +385,23 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private static byte AddMod(byte value, byte delta, byte modulus) private static byte AddMod(byte value, byte delta, byte modulus)
{ {
int result = value + delta; int result = value + delta;
if (result >= modulus) if (result >= modulus)
{ {
result -= modulus; result -= modulus;
if (result >= modulus)
{
result -= modulus;
}
} }
return (byte)result; return (byte)result;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod8(byte value, byte delta)
{
return (byte)((value + delta) & 7);
}
@@ -557,27 +564,25 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View; ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View;
GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor); GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor);
divisorView.SubView(0, 1).CopyFromCPU(ref partialData, 1); divisorView.CopyFromCPU(ref partialData, 1);
int offsetValue = 0; int offsetValue = 0;
offsetView.SubView(0, 1).CopyFromCPU(ref offsetValue, 1); offsetView.CopyFromCPU(ref offsetValue, 1);
int countValue = length; int countValue = length;
countView.SubView(0, 1).CopyFromCPU(ref countValue, 1); countView.CopyFromCPU(ref countValue, 1);
ulong cycleValue = cycle; ulong cycleValue = cycle;
cycleView.SubView(0, 1).CopyFromCPU(ref cycleValue, 1); cycleView.CopyFromCPU(ref cycleValue, 1);
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length); Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length);
primes.CopyTo(hostSpan); primes.CopyTo(hostSpan);
ref ulong hostRef = ref MemoryMarshal.GetReference(hostSpan);
ArrayView1D<ulong, Stride1D.Dense> exponentSlice = exponentView.SubView(0, length); exponentView.CopyFromCPU(ref hostRef, length);
exponentSlice.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), length);
ArrayView1D<byte, Stride1D.Dense> hitSliceView = hitView.SubView(0, length);
int sentinel = int.MaxValue; int sentinel = int.MaxValue;
firstHitView.SubView(0, 1).CopyFromCPU(ref sentinel, 1); firstHitView.CopyFromCPU(ref sentinel, 1);
var kernel = _kernel; var kernel = _kernel;
if (kernel is null) if (kernel is null)
@@ -586,10 +591,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
throw new InvalidOperationException("GPU divisor kernel was not initialized."); throw new InvalidOperationException("GPU divisor kernel was not initialized.");
} }
kernel(1, divisorView, offsetView, countView, exponentSlice, cycleView, hitSliceView, firstHitView); kernel(1, divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView);
Span<byte> hitSlice = hits.Slice(0, length); Span<byte> hitSlice = hits.Slice(0, length);
hitSliceView.CopyToCPU(ref MemoryMarshal.GetReference(hitSlice), length); ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice);
hitView.CopyToCPU(ref hitRef, length);
Monitor.Exit(_lease.ExecutionLock); Monitor.Exit(_lease.ExecutionLock);
} }
@@ -93,9 +93,15 @@ public static class MersenneNumberDivisorByDivisorTester
} }
else else
{ {
while (startIndex > 0 && candidates[startIndex - 1] >= startPrime) while (startIndex > 0)
{ {
startIndex--; int previousIndex = startIndex - 1;
if (candidates[previousIndex] < startPrime)
{
break;
}
startIndex = previousIndex;
} }
} }
} }
+1 -1
View File
@@ -136,7 +136,7 @@ The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels,
* [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.
8. [done] **Refactor by-divisor CPU/GPU testers to reuse heuristics** 8. [done] **Refactor by-divisor CPU/GPU testers to reuse heuristics**
* [done] Drove `MersenneNumberDivisorByDivisorCpuTester.CheckDivisors` from `HeuristicPrimeTester.CreateMersenneDivisorEnumerator`, eliminating the bespoke residue loop while preserving the existing status counters. * [done] Drove `MersenneNumberDivisorByDivisorCpuTester.CheckDivisors` from `HeuristicPrimeTester.CreateMersenneDivisorEnumerator`, eliminating the bespoke residue loop and retiring the legacy status counter.
* [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. * [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** 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] Introduce `HeuristicPrimeTester` so heuristic CPU/GPU checks live alongside the legacy `PrimeTester` implementations while callers opt in explicitly.
-1
View File
@@ -41,7 +41,6 @@ Montgomery Primitives
Cycle-Aware Modular Arithmetic Cycle-Aware Modular Arithmetic
Status & Progress Reporting Status & Progress Reporting
- PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs:296: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI status benchmarks so we avoid `%` in this hot loop while still wrapping progress correctly.
Cycle Remainder Steppers Cycle Remainder Steppers
- PerfectNumbers.Core/CycleRemainderStepper.cs:28: Inline this reset at the call sites so the hot loops reuse struct reinitialization measured fastest in MersenneDivisorCycleLengthGpuBenchmarks, avoiding the extra method call when scanners need to restart stepping. - PerfectNumbers.Core/CycleRemainderStepper.cs:28: Inline this reset at the call sites so the hot loops reuse struct reinitialization measured fastest in MersenneDivisorCycleLengthGpuBenchmarks, avoiding the extra method call when scanners need to restart stepping.