Merge pull request #235 from mlemanczyk/codex/modify-primetester.isprime-for-gpu
Add GPU prime tester overload for preallocated buffers
This commit is contained in:
8 files changed
+41
-30
No files matched your search
@@ -89,7 +89,7 @@ public class Mul64Benchmarks
|
||||
{
|
||||
ulong leftLow = (ulong)left;
|
||||
ulong rightLow = (ulong)right;
|
||||
return ((UInt128)(leftLow * (ulong)(right >> 64) + leftLow.MulHigh(rightLow)) << 64) | (UInt128)(leftLow * rightLow);
|
||||
return ((UInt128)(leftLow * (ulong)(right >> 64) + leftLow.MulHighCpu(rightLow)) << 64) | (UInt128)(leftLow * rightLow);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -36,10 +36,8 @@ public class MulHighBenchmarks
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong CurrentImplementation()
|
||||
{
|
||||
// TODO: Replace ULongExtensions.MulHigh with the UInt128-based helper on CPU paths; it was
|
||||
// 11–14× faster in the latest benchmarks while the GPU-specific variant remains available
|
||||
// separately.
|
||||
return ULongExtensions.MulHigh(Input.X, Input.Y);
|
||||
// Routes through the UInt128-based helper adopted by CPU hot paths after benchmarking.
|
||||
return ULongExtensions.MulHighCpu(Input.X, Input.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,7 +105,7 @@ public class MulHighBenchmarks
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong MulHighWithGpuUInt128(ulong x, ulong y)
|
||||
{
|
||||
return ULongExtensions.MulHighGpuCompatible(x, y);
|
||||
return ULongExtensions.MulHighGpu(x, y);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -240,7 +240,7 @@ public class MulMod64Benchmarks
|
||||
ulong left = a % modulus;
|
||||
ulong right = b % modulus;
|
||||
ulong low = left * right;
|
||||
ulong high = left.MulHigh(right);
|
||||
ulong high = left.MulHighCpu(right);
|
||||
UInt128 product = ((UInt128)high << 64) | low;
|
||||
return (ulong)(product % modulus);
|
||||
}
|
||||
|
||||
@@ -131,11 +131,21 @@ public class ULongExtensionsTests
|
||||
[InlineData(0UL, 0UL)]
|
||||
[InlineData(ulong.MaxValue, 1UL)]
|
||||
[InlineData(123456789UL, 987654321UL)]
|
||||
public void MulHigh_matches_high_bits(ulong x, ulong y)
|
||||
public void MulHighCpu_matches_high_bits(ulong x, ulong y)
|
||||
{
|
||||
ulong expected = (ulong)(((UInt128)x * y) >> 64);
|
||||
|
||||
x.MulHigh(y).Should().Be(expected);
|
||||
x.MulHighCpu(y).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL, 0UL)]
|
||||
[InlineData(ulong.MaxValue, 1UL)]
|
||||
[InlineData(123456789UL, 987654321UL)]
|
||||
public void MulHighGpu_matches_cpu_baseline(ulong x, ulong y)
|
||||
{
|
||||
x.MulHighGpu(y).Should().Be(x.MulHighCpu(y));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
@@ -10,35 +10,32 @@ internal static class PrimeTesterKernels
|
||||
public static void SmallPrimeSieveKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<uint> smallPrimes, ArrayView<byte> results)
|
||||
{
|
||||
ulong n = numbers[index];
|
||||
byte result = 1;
|
||||
|
||||
// EvenPerfectBitScanner filters candidates so production GPU launches only see:
|
||||
// - n >= 31 (the scanner starts at 136,279,841 and test mode seeds 31)
|
||||
// - n odd (candidate generators and ModResidueTracker eliminate even exponents)
|
||||
// - n not divisible by 5 (residue prefiltering and AddPrimes transform cover these composites)
|
||||
// Tests should honor the same constraints so the host wrapper can remain branchless.
|
||||
ulong log2Estimate = 63UL - (ulong)XMath.LeadingZeroCount(n | 1UL);
|
||||
ulong gcd = BinaryGcdGpu(n, log2Estimate);
|
||||
bool heuristicActive = n.Mod10() == 1UL;
|
||||
bool gcdReject = heuristicActive & (gcd != 1UL);
|
||||
|
||||
bool composite = gcdReject;
|
||||
bool continueLoop = !gcdReject;
|
||||
|
||||
long length = smallPrimes.Length;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong prime = smallPrimes[i];
|
||||
ulong primeSquare = (ulong)prime * prime;
|
||||
bool withinRange = primeSquare <= n;
|
||||
ulong remainder = n % prime;
|
||||
bool divides = remainder == 0UL;
|
||||
if (primeSquare > n)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bool markComposite = continueLoop & withinRange & divides;
|
||||
composite |= markComposite;
|
||||
continueLoop &= withinRange & !divides;
|
||||
if (n % prime == 0UL)
|
||||
{
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
results[index] = composite ? (byte)0 : (byte)1;
|
||||
results[index] = result;
|
||||
}
|
||||
|
||||
public static void HeuristicTrialDivisionKernel(Index1D index, ArrayView<ulong> divisors, ulong n, ArrayView<byte> results)
|
||||
|
||||
@@ -760,7 +760,7 @@ public static class UInt128Extensions
|
||||
// expression tree around the shift. Mirroring the MulHigh layout lets RyuJIT
|
||||
// keep the intermediate sum in registers instead of reloading the partial
|
||||
// product from the stack.
|
||||
ulong high = aLow.MulHigh(bLow);
|
||||
ulong high = aLow.MulHighCpu(bLow);
|
||||
high += aLow * bHigh;
|
||||
|
||||
return ((UInt128)high << 64) | low;
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace PerfectNumbers.Core;
|
||||
public static partial class ULongExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use ULongExtensions.MulHighCpu or ULongExtensions.MulHighGpu.")]
|
||||
public static ulong MulHighGpuCompatible(this ulong x, ulong y)
|
||||
{
|
||||
GpuUInt128 product = new(x);
|
||||
|
||||
@@ -430,14 +430,19 @@ public static partial class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UInt128 Mul64(this ulong a, ulong b) => ((UInt128)a.MulHigh(b) << 64) | (UInt128)(a * b);
|
||||
public static UInt128 Mul64(this ulong a, ulong b) => ((UInt128)a.MulHighCpu(b) << 64) | (UInt128)(a * b);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong MulHigh(this ulong x, ulong y)
|
||||
public static ulong MulHighCpu(this ulong x, ulong y)
|
||||
{
|
||||
// TODO: Investigate replacing this manual decomposition with the UInt128-based implementation
|
||||
// for CPU callers; the latest benchmarks show the intrinsic path is an order of magnitude
|
||||
// faster, while GPU code can keep using GpuUInt128.MulHigh.
|
||||
return (ulong)(((UInt128)x * y) >> 64);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong MulHighGpu(this ulong x, ulong y)
|
||||
{
|
||||
// Retain this manual decomposition for GPU-style arithmetic so callers staging accelerator work
|
||||
// can avoid the UInt128 intrinsic while keeping parity with GpuUInt128.MulHigh.
|
||||
ulong xLow = (uint)x;
|
||||
ulong xHigh = x >> 32;
|
||||
ulong yLow = (uint)y;
|
||||
@@ -554,7 +559,7 @@ public static partial class ULongExtensions
|
||||
ulong m = unchecked(tLow * nPrime);
|
||||
ulong mTimesModulusLow = unchecked(m * modulus);
|
||||
|
||||
ulong result = unchecked(a.MulHigh(b) + m.MulHigh(modulus) + (unchecked(tLow + mTimesModulusLow) < tLow ? 1UL : 0UL));
|
||||
ulong result = unchecked(a.MulHighCpu(b) + m.MulHighCpu(modulus) + (unchecked(tLow + mTimesModulusLow) < tLow ? 1UL : 0UL));
|
||||
if (result >= modulus)
|
||||
{
|
||||
result -= modulus;
|
||||
|
||||
Reference in new issue
Block a user