Split MulHigh extensions into CPU and GPU variants

This commit is contained in:
Marek Lemańczyk committed 2025-10-26 22:42:21 +01:00
1 parent 3d458fc6da
commit 0e1212aa1c
7 files changed
+32 -16

No files matched your search

@@ -89,7 +89,7 @@ public class Mul64Benchmarks
{ {
ulong leftLow = (ulong)left; ulong leftLow = (ulong)left;
ulong rightLow = (ulong)right; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -36,10 +37,7 @@ public class MulHighBenchmarks
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public ulong CurrentImplementation() public ulong CurrentImplementation()
{ {
// TODO: Replace ULongExtensions.MulHigh with the UInt128-based helper on CPU paths; it was return ULongExtensions.MulHighCpu(Input.X, Input.Y);
// 11–14× faster in the latest benchmarks while the GPU-specific variant remains available
// separately.
return ULongExtensions.MulHigh(Input.X, Input.Y);
} }
/// <summary> /// <summary>
@@ -107,7 +105,9 @@ public class MulHighBenchmarks
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong MulHighWithGpuUInt128(ulong x, ulong y) private static ulong MulHighWithGpuUInt128(ulong x, ulong y)
{ {
return ULongExtensions.MulHighGpuCompatible(x, y); GpuUInt128 product = new(x);
GpuUInt128.Mul64(ref product, 0UL, y);
return product.High;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -240,7 +240,7 @@ public class MulMod64Benchmarks
ulong left = a % modulus; ulong left = a % modulus;
ulong right = b % modulus; ulong right = b % modulus;
ulong low = left * right; ulong low = left * right;
ulong high = left.MulHigh(right); ulong high = left.MulHighCpu(right);
UInt128 product = ((UInt128)high << 64) | low; UInt128 product = ((UInt128)high << 64) | low;
return (ulong)(product % modulus); return (ulong)(product % modulus);
} }
@@ -131,11 +131,21 @@ public class ULongExtensionsTests
[InlineData(0UL, 0UL)] [InlineData(0UL, 0UL)]
[InlineData(ulong.MaxValue, 1UL)] [InlineData(ulong.MaxValue, 1UL)]
[InlineData(123456789UL, 987654321UL)] [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); 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] [Theory]
+1 -1
View File
@@ -760,7 +760,7 @@ public static class UInt128Extensions
// expression tree around the shift. Mirroring the MulHigh layout lets RyuJIT // expression tree around the shift. Mirroring the MulHigh layout lets RyuJIT
// keep the intermediate sum in registers instead of reloading the partial // keep the intermediate sum in registers instead of reloading the partial
// product from the stack. // product from the stack.
ulong high = aLow.MulHigh(bLow); ulong high = aLow.MulHighCpu(bLow);
high += aLow * bHigh; high += aLow * bHigh;
return ((UInt128)high << 64) | low; return ((UInt128)high << 64) | low;
@@ -11,6 +11,7 @@ namespace PerfectNumbers.Core;
public static partial class ULongExtensions public static partial class ULongExtensions
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use ULongExtensions.MulHighCpu or ULongExtensions.MulHighGpu.")]
public static ulong MulHighGpuCompatible(this ulong x, ulong y) public static ulong MulHighGpuCompatible(this ulong x, ulong y)
{ {
GpuUInt128 product = new(x); GpuUInt128 product = new(x);
+11 -6
View File
@@ -430,14 +430,19 @@ public static partial class ULongExtensions
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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)] [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 return (ulong)(((UInt128)x * y) >> 64);
// for CPU callers; the latest benchmarks show the intrinsic path is an order of magnitude }
// faster, while GPU code can keep using GpuUInt128.MulHigh.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong MulHighGpu(this ulong x, ulong y)
{
// The CPU helpers rely on UInt128 via MulHighCpu; retain this manual decomposition for GPU-style
// arithmetic so callers can avoid the intrinsic when staging work for accelerators.
ulong xLow = (uint)x; ulong xLow = (uint)x;
ulong xHigh = x >> 32; ulong xHigh = x >> 32;
ulong yLow = (uint)y; ulong yLow = (uint)y;
@@ -554,7 +559,7 @@ public static partial class ULongExtensions
ulong m = unchecked(tLow * nPrime); ulong m = unchecked(tLow * nPrime);
ulong mTimesModulusLow = unchecked(m * modulus); 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) if (result >= modulus)
{ {
result -= modulus; result -= modulus;