Inline GpuUInt128 Mul64 helper

This commit is contained in:
Marek Lemańczyk committed 2025-10-20 13:30:34 +02:00
1 parent 9c08cf48a7
commit 1809ba95b3
8 files changed
+108 -114

No files matched your search

@@ -33,7 +33,7 @@ public class GpuUInt128Mul64Benchmarks
public GpuUInt128 HighProductMaterializedInLocal()
{
GpuUInt128 value = Input.Multiplicand;
value.Mul64(Input.Multiplier);
GpuUInt128.Mul64(ref value, Input.Multiplier.High, Input.Multiplier.Low);
return value;
}
@@ -1,3 +1,4 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
@@ -67,7 +68,6 @@ public class GpuUInt128MulModBenchmarks
[ArgumentsSource(nameof(GetGeneralCases))]
public GpuUInt128 InPlaceMulMod(GpuMulModCase input)
{
// TODO: Switch the extension method to allocate per iteration, as it's faster. Keep the benchmarks.
GpuUInt128 value = input.Left;
value.MulMod(input.Right, input.Modulus);
return value;
@@ -87,6 +87,16 @@ public class GpuUInt128MulModBenchmarks
return MulModInline(input.Left, input.Right, input.Modulus);
}
/// <summary>
/// BigInteger-based reference reduction kept for validation; remains several microseconds slower than the GPU-compatible implementations.
/// </summary>
[Benchmark]
[ArgumentsSource(nameof(GetGeneralCases))]
public GpuUInt128 BigIntegerReference(GpuMulModCase input)
{
return MulModBigInteger(input.Left, input.Right, input.Modulus);
}
/// <summary>
/// Legacy implementation that allocates intermediate limbs; delivered 10.7 ns on tiny operands, 12.7 ns on high-word-dominant
/// pairs, and 16.3 ns on mixed magnitude, making it the faster choice overall.
@@ -112,10 +122,7 @@ public class GpuUInt128MulModBenchmarks
[ArgumentsSource(nameof(GetByLimbCases))]
public GpuUInt128 InPlaceReduction(GpuMulModCase input)
{
// TODO: Switch the extension method back to legacy method, as it's faster. Keep the benchmarks.
GpuUInt128 value = input.Left;
value.MulModByLimb(input.Right, input.Modulus);
return value;
return MulModByLimbInPlace(input.Left, input.Right, input.Modulus);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -158,6 +165,46 @@ public class GpuUInt128MulModBenchmarks
return result;
}
private static GpuUInt128 MulModBigInteger(GpuUInt128 left, GpuUInt128 right, GpuUInt128 modulus)
{
BigInteger leftBig = (UInt128)left;
BigInteger rightBig = (UInt128)right;
BigInteger modulusBig = (UInt128)modulus;
if (modulusBig.IsZero)
{
return GpuUInt128.Zero;
}
UInt128 reduced = (UInt128)((leftBig * rightBig) % modulusBig);
return new GpuUInt128((ulong)(reduced >> 64), (ulong)reduced);
}
private static GpuUInt128 MulModByLimbInPlace(GpuUInt128 left, GpuUInt128 right, GpuUInt128 modulus)
{
var (p3, p2, p1, p0) = MultiplyFullLegacy(left, right);
GpuUInt128 remainder = new(p3, p2);
while (remainder.CompareTo(modulus) >= 0)
{
remainder.Sub(modulus);
}
ulong limb = p1;
for (int i = 0; i < 2; i++)
{
remainder.ShiftLeft(64);
remainder.Low = limb;
while (remainder.CompareTo(modulus) >= 0)
{
remainder.Sub(modulus);
}
limb = p0;
}
return remainder;
}
private static GpuUInt128 MulModByLimbLegacy(GpuUInt128 left, GpuUInt128 right, GpuUInt128 modulus)
{
var (p3, p2, p1, p0) = MultiplyFullLegacy(left, right);
@@ -157,9 +157,8 @@ public class MulMod64Benchmarks
}
/// <summary>
/// Uses the GPU-friendly <see cref="ULongExtensions.MulMod64Gpu(ulong, ulong, ulong)"/> helper; dense inputs now
/// cost 24.4–61.9 ns, while NearFullRange and SparseOperands finish near 7.27 ns and ZeroOperands stay baseline-fast at
/// 3.59 ns.
/// Benchmarks the legacy GPU-oriented shim that previously powered <see cref="ULongExtensions.MulMod64Gpu(ulong, ulong, ulong)"/>;
/// dense inputs still land in the 24.4–61.9 ns range while sparse operands remain closer to 3.6–7.3 ns.
/// </summary>
/// <remarks>
/// Observed means: CrossWordBlend 24.422 ns (6.86×), MixedBitPattern 61.850 ns, NearFullRange 7.267 ns,
@@ -168,14 +167,12 @@ public class MulMod64Benchmarks
[Benchmark]
public ulong GpuCompatibleBaseline()
{
// TODO: Callers in production should migrate to ULongExtensions.MulMod64 where GPU parity
// is not required; that baseline stayed 6–17× faster for dense 64-bit operands.
return Input.Left.MulMod64Gpu(Input.Right, Input.Modulus);
return MulMod64GpuLegacy(Input.Left, Input.Right, Input.Modulus);
}
/// <summary>
/// Uses the deferred native-modulo helper <see cref="ULongExtensions.MulMod64GpuDeferred(ulong, ulong, ulong)"/>;
/// excels when operands are tiny (2.01 ns on ZeroOperands) yet remains the slowest choice on dense data at 146–295 ns.
/// Benchmarks the deferred native-modulo shim that now lives only in this suite; tiny operands still excel around 2 ns
/// while dense datasets take 146–295 ns.
/// </summary>
/// <remarks>
/// Observed means: CrossWordBlend 146.559 ns (40.63×), MixedBitPattern 255.555 ns, NearFullRange 21.390 ns,
@@ -184,9 +181,21 @@ public class MulMod64Benchmarks
[Benchmark]
public ulong GpuCompatibleDeferred()
{
// TODO: Retire the deferred GPU shim from runtime paths and keep it only for benchmarks;
// ULongExtensions.MulMod64 avoids the 6×–82× slowdown on real-world operand mixes.
return Input.Left.MulMod64GpuDeferred(Input.Right, Input.Modulus);
return MulMod64GpuDeferredLegacy(Input.Left, Input.Right, Input.Modulus);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong MulMod64GpuLegacy(ulong a, ulong b, ulong modulus)
{
GpuUInt128 state = new(a % modulus);
return state.MulMod(b, modulus);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong MulMod64GpuDeferredLegacy(ulong a, ulong b, ulong modulus)
{
GpuUInt128 state = new(a);
return state.MulModWithNativeModulo(b, modulus);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]