diff --git a/PerfectNumbers.Core/Gpu/Kernels/IncrementalKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/IncrementalKernels.cs index 2d8ff70..41f001b 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/IncrementalKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/IncrementalKernels.cs @@ -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 found, ArrayView1D 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; diff --git a/PerfectNumbers.Core/Gpu/Kernels/Pow2ModKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/Pow2ModKernels.cs index a683c73..16fe047 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/Pow2ModKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/Pow2ModKernels.cs @@ -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 orders, - ArrayView1D smallCycles, - ArrayView1D smallPrimesLastOne, - ArrayView1D smallPrimesLastSeven, - ArrayView1D smallPrimesPow2LastOne, - ArrayView1D 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 orders, + ArrayView1D smallCycles, + ArrayView1D smallPrimesLastOne, + ArrayView1D smallPrimesLastSeven, + ArrayView1D smallPrimesPow2LastOne, + ArrayView1D 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 primes = lastIsSeven != 0 ? smallPrimesLastSeven : smallPrimesLastOne; - ArrayView1D 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 primes = lastIsSeven != 0 ? smallPrimesLastSeven : smallPrimesLastOne; + ArrayView1D 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 found, - ArrayView1D 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 found, + ArrayView1D 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); + } } diff --git a/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderKernels.cs b/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderKernels.cs index 194d195..044e510 100644 --- a/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderKernels.cs +++ b/PerfectNumbers.Core/Gpu/Kernels/PrimeOrderKernels.cs @@ -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 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 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 phiFactors = buffers.PhiFactors; ArrayView1D phiExponents = buffers.PhiExponents; ArrayView1D workFactors = buffers.WorkFactors; ArrayView1D 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 exponents, MontgomeryDivisorData divisor, ArrayView1D 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 exponents, MontgomeryDivisorData divisor, ArrayView1D remainders) + { + ulong exponent = exponents[index]; + remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent); + } private static Action, GpuUInt128, ArrayView1D> 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 exponents, GpuUInt128 modulus, ArrayView1D remainders) { GpuUInt128 exponent = exponents[index]; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs index db1d688..93b52b3 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs @@ -41,7 +41,8 @@ public sealed class MersenneNumberDivisorGpuTester var accelerator = gpu.Accelerator; var kernel = GetKernel(accelerator); var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D(1)); - resultBuffer.MemSetToZero(); + // There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0] + // resultBuffer.MemSetToZero(); kernel(1, exponent, divisor, resultBuffer.View); accelerator.Synchronize(); Span result = stackalloc byte[1]; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs index 4a237f3..d52970f 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs @@ -35,6 +35,7 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO { remaining = maxK - kStart + 1UL; currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; + // This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result. foundBuffer.MemSetToZero(); // Precompute residue automaton bases for this batch UInt128 q0 = twoP * kStart + 1UL; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index 12f1375..91a6b48 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -143,8 +143,9 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) while (bag.TryTake(out var resources)) { if (resources.Capacity >= capacity) - { - resources.OrderBuffer.MemSetToZero(); + { + // The caller always overwrites the required elements. We don't need to worry about clearing the output buffer. + // resources.OrderBuffer.MemSetToZero(); return resources; } @@ -152,7 +153,8 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) } var created = new ResidueResources(accelerator, capacity); - created.OrderBuffer.MemSetToZero(); + // The caller always overwrites the required elements. We don't need to worry about clearing the output buffer. + // created.OrderBuffer.MemSetToZero(); return created; } diff --git a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs index 2a17ce2..7b17b48 100644 --- a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs +++ b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs @@ -199,19 +199,21 @@ internal static partial class PrimeOrderGpuHeuristics AcceleratorStream stream = lease.Stream; var kernel = GetPartialFactorKernel(accelerator); - SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator); + SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator); + // TODO: We should create / reallocate these buffer only once or if the new required length is bigger than capacity. var factorBuffer = accelerator.Allocate1D(primeTargets.Length); var exponentBuffer = accelerator.Allocate1D(exponentTargets.Length); var countBuffer = accelerator.Allocate1D(1); var remainingBuffer = accelerator.Allocate1D(1); var fullyFactoredBuffer = accelerator.Allocate1D(1); - factorBuffer.MemSetToZero(); - exponentBuffer.MemSetToZero(); - countBuffer.MemSetToZero(); - remainingBuffer.MemSetToZero(); - fullyFactoredBuffer.MemSetToZero(); + // TODO: There is no need to clear these buffers because the kernel will always assign values within the required bounds. + // factorBuffer.MemSetToZero(); + // exponentBuffer.MemSetToZero(); + // countBuffer.MemSetToZero(); + // remainingBuffer.MemSetToZero(); + // fullyFactoredBuffer.MemSetToZero(); kernel( stream, @@ -240,6 +242,7 @@ internal static partial class PrimeOrderGpuHeuristics fullyFactoredBuffer.View.CopyToCPU(ref fullyFactoredFlag, 1); fullyFactored = fullyFactoredFlag != 0; + // TODO: These buffers shouldn't be disposed but rather reused accross call with the assigned accelerator. factorBuffer.Dispose(); exponentBuffer.Dispose(); countBuffer.Dispose(); @@ -364,8 +367,11 @@ internal static partial class PrimeOrderGpuHeuristics AcceleratorStream stream = lease.Stream; var kernel = GetOrderKernel(accelerator); - SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator); + SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator); + // TODO: These buffers should be created reallocated once and assigned to an accelerator. Callers should use their own + // thread static cache to prevent other threads from using taking accelerators with pre-allocated buffers if they don't + // use them. var phiFactorBuffer = accelerator.Allocate1D(GpuSmallPrimeFactorSlots); var phiExponentBuffer = accelerator.Allocate1D(GpuSmallPrimeFactorSlots); var workFactorBuffer = accelerator.Allocate1D(GpuSmallPrimeFactorSlots); @@ -377,7 +383,8 @@ internal static partial class PrimeOrderGpuHeuristics var resultBuffer = accelerator.Allocate1D(1); var statusBuffer = accelerator.Allocate1D(1); - phiFactorBuffer.MemSetToZero(); + // TODO: Remove the cleaning after the order kernel is modified to always set the result. + phiFactorBuffer.MemSetToZero(); phiExponentBuffer.MemSetToZero(); workFactorBuffer.MemSetToZero(); workExponentBuffer.MemSetToZero(); @@ -523,26 +530,31 @@ internal static partial class PrimeOrderGpuHeuristics Accelerator accelerator = lease.Accelerator; AcceleratorStream stream = lease.Stream; var kernel = GetPow2ModKernel(accelerator); + // TODO: These buffers should be allocated once per accelerator and only reallocated when the new length exceeds capacity. + // Modify the callers to use their own pool of buffers per accelerator, so that other threads don't use the accelerators + // with out preallocated buffers. Share the pool with Pow2ModWide kernel. var exponentBuffer = accelerator.Allocate1D(exponents.Length); var remainderBuffer = accelerator.Allocate1D(exponents.Length); exponentBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(exponents), exponents.Length); - remainderBuffer.MemSetToZero(); - try - { - kernel(stream, exponents.Length, exponentBuffer.View, divisorData, remainderBuffer.View); - stream.Synchronize(); - } - catch (Exception) - { - Console.WriteLine($"Exception for {prime} and exponents: {string.Join(",", exponents.ToArray())}."); - exponentBuffer.Dispose(); - remainderBuffer.Dispose(); - execution.Dispose(); - lease.Dispose(); - throw; - } + // Pow2Mod kernel always assigns the required output elements so we don't need to worry about clearing these. + // remainderBuffer.MemSetToZero(); + + try + { + kernel(stream, exponents.Length, exponentBuffer.View, divisorData, remainderBuffer.View); + stream.Synchronize(); + } + catch (Exception) + { + Console.WriteLine($"Exception for {prime} and exponents: {string.Join(",", exponents.ToArray())}."); + exponentBuffer.Dispose(); + remainderBuffer.Dispose(); + execution.Dispose(); + lease.Dispose(); + throw; + } remainderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), exponents.Length); exponentBuffer.Dispose(); @@ -579,6 +591,9 @@ internal static partial class PrimeOrderGpuHeuristics Accelerator accelerator = lease.Accelerator; AcceleratorStream stream = lease.Stream; var kernel = GetPow2ModWideKernel(accelerator); + // TODO: These buffers should be allocated once per accelerator and only reallocated when the new length exceeds capacity. + // Modify the callers to use their own pool of buffers per accelerator, so that other threads don't use the accelerators + // with out preallocated buffers. Share the pool with Pow2Mod kernel. var exponentBuffer = accelerator.Allocate1D(length); var remainderBuffer = accelerator.Allocate1D(length); @@ -592,7 +607,8 @@ internal static partial class PrimeOrderGpuHeuristics } exponentBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), length); - remainderBuffer.MemSetToZero(); + // Pow2ModWide kernel always assigns the required output elements so we don't need to worry about clearing these. + // remainderBuffer.MemSetToZero(); GpuUInt128 modulus = (GpuUInt128)prime; kernel(stream, length, exponentBuffer.View, modulus, remainderBuffer.View);