Unnecessary memory zeroing commented out. TODOs added for the remainders.

This commit is contained in:
mlemanczyk committed 2025-10-23 19:42:03 +02:00
1 parent 8e3e990499
commit 0d7df2e45b
7 files changed
+246 -208

No files matched your search

@@ -68,7 +68,6 @@ internal static class IncrementalKernels
return;
}
// TODO: Is this expected that the value of kStart is modified?
GpuUInt128 k = kStart + (GpuUInt128)idx;
GpuUInt128 q = twoP;
GpuUInt128.Mul64(ref q, k.High, k.Low);
@@ -125,13 +124,16 @@ internal static class IncrementalKernels
ulong exponent,
GpuUInt128 twoP,
GpuUInt128 kStart,
byte lastIsSeven,
byte _,
ulong divMul,
ResidueAutomatonArgs ra,
ArrayView<int> found,
ArrayView1D<ulong, Stride1D.Dense> smallCycles)
{
{
// TODO: Modify this kernel to always assign the required found element to the result of the calculations and
// remove any clearing of the output buffer in the caller methods.
ulong idx = (ulong)index.X;
// TODO: Replace % operator in GPU and their outgoing paths with logical operations, where possible.
ulong idxMod3 = idx % 3UL;
ulong idxMod5 = idx % 5UL;
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
+167 -163
View File
@@ -6,173 +6,177 @@ namespace PerfectNumbers.Core.Gpu;
internal static class Pow2ModKernels
{
public static void Pow2ModKernelScan(
Index1D index,
ulong exponent,
GpuUInt128 twoP,
GpuUInt128 kStart,
byte lastIsSeven,
ulong _,
ResidueAutomatonArgs ra,
ArrayView<ulong> orders,
ArrayView1D<ulong, Stride1D.Dense> smallCycles,
ArrayView1D<uint, Stride1D.Dense> smallPrimesLastOne,
ArrayView1D<uint, Stride1D.Dense> smallPrimesLastSeven,
ArrayView1D<ulong, Stride1D.Dense> smallPrimesPow2LastOne,
ArrayView1D<ulong, Stride1D.Dense> smallPrimesPow2LastSeven)
{
ulong idx = (ulong)index.X;
ulong idxMod3 = idx % 3UL;
ulong idxMod5 = idx % 5UL;
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
bool shouldCheck = r10 != 5UL;
if (shouldCheck)
{
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
if (r8 != 1UL && r8 != 7UL)
{
shouldCheck = false;
}
else
{
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
if (r3 >= 6UL) r3 -= 6UL;
if (r3 >= 3UL) r3 -= 3UL;
if (r3 == 0UL)
{
shouldCheck = false;
}
else
{
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
if (r5 >= 15UL) r5 -= 15UL;
if (r5 >= 10UL) r5 -= 10UL;
if (r5 >= 5UL) r5 -= 5UL;
if (r5 == 0UL)
{
shouldCheck = false;
}
}
}
}
if (!shouldCheck)
{
orders[index] = 0UL;
return;
}
/// This kernel always sets the result of the corresponding element. Callers don't need to clear the output buffers.
public static void Pow2ModKernelScan(
Index1D index,
ulong exponent,
GpuUInt128 twoP,
GpuUInt128 kStart,
byte lastIsSeven,
ulong _,
ResidueAutomatonArgs ra,
ArrayView<ulong> orders,
ArrayView1D<ulong, Stride1D.Dense> smallCycles,
ArrayView1D<uint, Stride1D.Dense> smallPrimesLastOne,
ArrayView1D<uint, Stride1D.Dense> smallPrimesLastSeven,
ArrayView1D<ulong, Stride1D.Dense> smallPrimesPow2LastOne,
ArrayView1D<ulong, Stride1D.Dense> smallPrimesPow2LastSeven)
{
ulong idx = (ulong)index.X;
// TODO: Replace % operator in GPU and their outgoing paths with logical operations, where possible.
ulong idxMod3 = idx % 3UL;
ulong idxMod5 = idx % 5UL;
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
bool shouldCheck = r10 != 5UL;
// TODO: Replace these "if" statements with ?:
if (shouldCheck)
{
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
if (r8 != 1UL && r8 != 7UL)
{
shouldCheck = false;
}
else
{
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
if (r3 >= 6UL) r3 -= 6UL;
if (r3 >= 3UL) r3 -= 3UL;
if (r3 == 0UL)
{
shouldCheck = false;
}
else
{
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
if (r5 >= 15UL) r5 -= 15UL;
if (r5 >= 10UL) r5 -= 10UL;
if (r5 >= 5UL) r5 -= 5UL;
if (r5 == 0UL)
{
shouldCheck = false;
}
}
}
}
if (!shouldCheck)
{
orders[index] = 0UL;
return;
}
// TODO: kStart is modified after this. Is this expected?
kStart.Add(idx);
GpuUInt128 q = twoP;
GpuUInt128.Mul64(ref q, kStart.High, kStart.Low);
q.Add(GpuUInt128.One);
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{
ulong cycle = smallCycles[(int)q.Low];
// cycle should always be initialized if we're within array limit in production code
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{
orders[index] = 0UL;
return;
}
}
if (GpuUInt128.Pow2Minus1Mod(exponent, in readOnlyQ) != GpuUInt128.Zero)
{
orders[index] = 0UL;
return;
}
kStart.Add(idx);
GpuUInt128 q = twoP;
GpuUInt128.Mul64(ref q, kStart.High, kStart.Low);
q.Add(GpuUInt128.One);
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{
ulong cycle = smallCycles[(int)q.Low];
// cycle should always be initialized if we're within array limit in production code
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{
orders[index] = 0UL;
return;
}
}
if (GpuUInt128.Pow2Minus1Mod(exponent, in readOnlyQ) != GpuUInt128.Zero)
{
orders[index] = 0UL;
return;
}
ArrayView1D<uint, Stride1D.Dense> primes = lastIsSeven != 0 ? smallPrimesLastSeven : smallPrimesLastOne;
ArrayView1D<ulong, Stride1D.Dense> primesPow2 = lastIsSeven != 0 ? smallPrimesPow2LastSeven : smallPrimesPow2LastOne;
int primesLen = (int)primes.Length;
for (int i = 0; i < primesLen; i++)
{
ulong square = primesPow2[i];
if (new GpuUInt128(0UL, square) > q)
{
break;
}
ulong prime = primes[i];
if (KernelMathHelpers.Mod128By64(q, prime) == 0UL)
{
orders[index] = 0UL;
return;
}
}
ArrayView1D<uint, Stride1D.Dense> primes = lastIsSeven != 0 ? smallPrimesLastSeven : smallPrimesLastOne;
ArrayView1D<ulong, Stride1D.Dense> primesPow2 = lastIsSeven != 0 ? smallPrimesPow2LastSeven : smallPrimesPow2LastOne;
int primesLen = (int)primes.Length;
for (int i = 0; i < primesLen; i++)
{
ulong square = primesPow2[i];
if (new GpuUInt128(0UL, square) > q)
{
break;
}
ulong prime = primes[i];
if (KernelMathHelpers.Mod128By64(q, prime) == 0UL)
{
orders[index] = 0UL;
return;
}
}
orders[index] = exponent;
}
orders[index] = exponent;
}
public static void Pow2ModOrderKernelScan(
Index1D index,
ulong exponent,
GpuUInt128 twoP,
GpuUInt128 kStart,
byte lastIsSeven,
ulong _,
ResidueAutomatonArgs ra,
ArrayView<int> found,
ArrayView1D<ulong, Stride1D.Dense> smallCycles)
{
ulong idx = (ulong)index.X;
ulong idxMod3 = idx % 3UL;
ulong idxMod5 = idx % 5UL;
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
bool shouldCheck = r10 != 5UL;
if (shouldCheck)
{
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
if (r8 != 1UL && r8 != 7UL)
{
shouldCheck = false;
}
else
{
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
if (r3 >= 6UL) r3 -= 6UL;
if (r3 >= 3UL) r3 -= 3UL;
if (r3 == 0UL)
{
shouldCheck = false;
}
else
{
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
if (r5 >= 15UL) r5 -= 15UL;
if (r5 >= 10UL) r5 -= 10UL;
if (r5 >= 5UL) r5 -= 5UL;
if (r5 == 0UL)
{
shouldCheck = false;
}
}
}
}
if (!shouldCheck)
{
return;
}
/// This kernel doesn't always set the result of the found array. It sets the value only when it needs to. The callers
/// must clean the output buffer before the call to get a deterministic result.
public static void Pow2ModOrderKernelScan(
Index1D index,
ulong exponent,
GpuUInt128 twoP,
GpuUInt128 kStart,
byte _,
ulong __,
ResidueAutomatonArgs ra,
ArrayView<int> found,
ArrayView1D<ulong, Stride1D.Dense> smallCycles)
{
ulong idx = (ulong)index.X;
ulong idxMod3 = idx % 3UL;
ulong idxMod5 = idx % 5UL;
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
bool shouldCheck = r10 != 5UL;
if (shouldCheck)
{
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
if (r8 != 1UL && r8 != 7UL)
{
shouldCheck = false;
}
else
{
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
if (r3 >= 6UL) r3 -= 6UL;
if (r3 >= 3UL) r3 -= 3UL;
if (r3 == 0UL)
{
shouldCheck = false;
}
else
{
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
if (r5 >= 15UL) r5 -= 15UL;
if (r5 >= 10UL) r5 -= 10UL;
if (r5 >= 5UL) r5 -= 5UL;
if (r5 == 0UL)
{
shouldCheck = false;
}
}
}
}
if (!shouldCheck)
{
return;
}
GpuUInt128 k = kStart + (GpuUInt128)idx;
GpuUInt128 q = twoP;
GpuUInt128.Mul64(ref q, k.High, k.Low);
q.Add(GpuUInt128.One);
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
// Small-cycles in-kernel early rejection from device table
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{
ulong cycle = smallCycles[(int)q.Low];
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{
return;
}
}
if (GpuUInt128.Pow2Mod(exponent, in readOnlyQ) != GpuUInt128.One)
{
return;
}
GpuUInt128 k = kStart + (GpuUInt128)idx;
GpuUInt128 q = twoP;
GpuUInt128.Mul64(ref q, k.High, k.Low);
q.Add(GpuUInt128.One);
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
// Small-cycles in-kernel early rejection from device table
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
{
ulong cycle = smallCycles[(int)q.Low];
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
{
return;
}
}
if (GpuUInt128.Pow2Mod(exponent, in readOnlyQ) != GpuUInt128.One)
{
return;
}
Atomic.Or(ref found[0], 1);
}
Atomic.Or(ref found[0], 1);
}
}
@@ -10,6 +10,8 @@ namespace PerfectNumbers.Core.Gpu;
internal static partial class PrimeOrderGpuHeuristics
{
/// This kernel always sets the result and operates within the allowed / required bounds, never accessing elements outside of them.
/// It sets the value only when it needs to. The callers don't need to clear the output / input buffers.
internal static void PartialFactorKernel(
Index1D index,
ArrayView1D<uint, Stride1D.Dense> primes,
@@ -111,6 +113,8 @@ internal static partial class PrimeOrderGpuHeuristics
}
}
/// This kernel doesn't always set the result of the first element. It sets the value only when it needs to. The callers
/// must clean the output buffer before the call to get a deterministic result.
internal static void CalculateOrderKernel(
Index1D index,
ulong prime,
@@ -126,7 +130,9 @@ internal static partial class PrimeOrderGpuHeuristics
return;
}
ArrayView1D<ulong, Stride1D.Dense> phiFactors = buffers.PhiFactors;
// TODO: Modify this kernel to always set the output result so that callers never need to clear any buffers and remove
// any existing cleaning of the buffers after that.
ArrayView1D<ulong, Stride1D.Dense> phiFactors = buffers.PhiFactors;
ArrayView1D<int, Stride1D.Dense> phiExponents = buffers.PhiExponents;
ArrayView1D<ulong, Stride1D.Dense> workFactors = buffers.WorkFactors;
ArrayView1D<int, Stride1D.Dense> workExponents = buffers.WorkExponents;
@@ -143,15 +149,18 @@ internal static partial class PrimeOrderGpuHeuristics
int maxPowChecks = config.MaxPowChecks;
int mode = config.Mode;
statusOut[0] = (byte)PrimeOrderKernelStatus.Fallback;
statusOut[0] = (byte)PrimeOrderKernelStatus.Fallback;
if (prime <= 3UL)
{
ulong orderValue = prime == 3UL ? 2UL : 1UL;
resultOut[0] = orderValue;
statusOut[0] = (byte)PrimeOrderKernelStatus.Found;
return;
}
// TODO: Is this condition ever satisfied on EventPerfectBitScanner's execution paths? Comment it out with explanatory
// comment before it, if not. Otherwise add a comment explaining how it's used. If tests or benchmarks require this,
// modify such tests and benchmark to avoid that.
if (prime <= 3UL)
{
ulong orderValue = prime == 3UL ? 2UL : 1UL;
resultOut[0] = orderValue;
statusOut[0] = (byte)PrimeOrderKernelStatus.Found;
return;
}
ulong phi = prime - 1UL;
@@ -1153,11 +1162,13 @@ internal static partial class PrimeOrderGpuHeuristics
return order;
}
internal static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
{
ulong exponent = exponents[index];
remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent);
}
/// This kernel always sets the result of the corresponding element. Callers don't need to clear the output buffers.
internal static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
{
ulong exponent = exponents[index];
remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent);
}
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
{
@@ -1169,6 +1180,7 @@ internal static partial class PrimeOrderGpuHeuristics
});
}
/// This kernel always sets the result of the corresponding element. Callers don't need to clear the output buffers.
internal static void Pow2ModKernelWide(Index1D index, ArrayView1D<GpuUInt128, Stride1D.Dense> exponents, GpuUInt128 modulus, ArrayView1D<GpuUInt128, Stride1D.Dense> remainders)
{
GpuUInt128 exponent = exponents[index];