Return scalar MulMod results and benchmark native reducers
This commit is contained in:
1 parent
4bbdebc874
commit
604e91b3e3
15 files changed
+1043
-261
No files matched your search
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuPow2ModBenchmarks
|
||||
{
|
||||
private static readonly Pow2ModInput[] Inputs = new Pow2ModInput[]
|
||||
{
|
||||
new(63UL, new GpuUInt128(0UL, 97UL), "SmallExponentSmallModulus"),
|
||||
new(1_048_575UL, new GpuUInt128(0UL, 0xFFFF_FFFBUL), "MediumExponentPrimeModulus"),
|
||||
new(6_710_886_400UL, new GpuUInt128(0x0000_0000_0000_0001UL, 0xFFFF_FFFF_FFFF_FFC3UL), "LargeExponentHighWordModulus"),
|
||||
new(ulong.MaxValue, new GpuUInt128(0xFFFF_FFFF_FFFF_FFFBUL, 0xFFFF_FFFF_FFFF_FFC5UL), "FullWidthExponentFullWidthModulus"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public Pow2ModInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<Pow2ModInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 ProcessEightBitWindows()
|
||||
{
|
||||
return GpuUInt128.Pow2Mod(Input.Exponent, Input.Modulus);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public GpuUInt128 ProcessSingleBits()
|
||||
{
|
||||
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
||||
}
|
||||
|
||||
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, in GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.IsZero || modulus == GpuUInt128.One)
|
||||
{
|
||||
return new GpuUInt128();
|
||||
}
|
||||
|
||||
GpuUInt128 result = new(1UL);
|
||||
GpuUInt128 baseVal = new(2UL);
|
||||
|
||||
ulong e = exponent;
|
||||
while (e != 0UL)
|
||||
{
|
||||
if ((e & 1UL) != 0UL)
|
||||
{
|
||||
result.MulMod(baseVal, modulus);
|
||||
}
|
||||
|
||||
baseVal.MulMod(baseVal, modulus);
|
||||
e >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public readonly record struct Pow2ModInput(ulong Exponent, GpuUInt128 Modulus, string Name)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128AddBenchmarks
|
||||
{
|
||||
private static readonly AddInput[] Inputs = new AddInput[]
|
||||
{
|
||||
new(new GpuUInt128(0UL, 0UL), new GpuUInt128(0UL, 1UL), "IncrementLow"),
|
||||
new(new GpuUInt128(0UL, ulong.MaxValue), new GpuUInt128(0UL, 1UL), "CarryIntoHigh"),
|
||||
new(new GpuUInt128(1UL, ulong.MaxValue), new GpuUInt128(3UL, 7UL), "MixedOperands"),
|
||||
new(new GpuUInt128(ulong.MaxValue, ulong.MaxValue), new GpuUInt128(ulong.MaxValue, ulong.MaxValue), "AllBitsSet"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public AddInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<AddInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 CarryMaterialisedInLocal()
|
||||
{
|
||||
GpuUInt128 value = Input.Left;
|
||||
value.Add(Input.Right);
|
||||
return value;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public GpuUInt128 CarryInExpression()
|
||||
{
|
||||
GpuUInt128 value = Input.Left;
|
||||
AddInline(ref value, Input.Right);
|
||||
return value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AddInline(ref GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
ulong low = left.Low + right.Low;
|
||||
left.High = left.High + right.High + (low < left.Low ? 1UL : 0UL);
|
||||
left.Low = low;
|
||||
}
|
||||
|
||||
public readonly record struct AddInput(GpuUInt128 Left, GpuUInt128 Right, string Name)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128BinaryGcdBenchmarks
|
||||
{
|
||||
private static readonly BinaryGcdInput[] Inputs = new BinaryGcdInput[]
|
||||
{
|
||||
new(new GpuUInt128(0UL, 48UL), new GpuUInt128(0UL, 18UL), "SmallOperands"),
|
||||
new(
|
||||
new GpuUInt128(0x0123_4567_89AB_CDEFUL, 0xFEDC_BA98_7654_3210UL),
|
||||
new GpuUInt128(0x0FED_CBA9_8765_4321UL, 0x0123_4567_89AB_CDEFUL),
|
||||
"HighEntropy"),
|
||||
new(new GpuUInt128(0UL, ulong.MaxValue), new GpuUInt128(0UL, ulong.MaxValue - 1UL), "LowWordHeavy"),
|
||||
new(new GpuUInt128(0x8000_0000_0000_0000UL, 0UL), new GpuUInt128(0x7FFF_FFFF_FFFF_FFFFUL, 0UL), "HighWordOnly"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public BinaryGcdInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<BinaryGcdInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 ReusedVariables()
|
||||
{
|
||||
return GpuUInt128.BinaryGcd(Input.Left, Input.Right);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public GpuUInt128 TemporaryStructPerIteration()
|
||||
{
|
||||
return BinaryGcdWithTemporary(Input.Left, Input.Right);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static GpuUInt128 BinaryGcdWithTemporary(GpuUInt128 u, GpuUInt128 v)
|
||||
{
|
||||
if (u.IsZero)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
if (v.IsZero)
|
||||
{
|
||||
return u;
|
||||
}
|
||||
|
||||
int shift = GpuUInt128.TrailingZeroCount(new GpuUInt128(u.High | v.High, u.Low | v.Low));
|
||||
int zu = GpuUInt128.TrailingZeroCount(u);
|
||||
u >>= zu;
|
||||
|
||||
do
|
||||
{
|
||||
int zv = GpuUInt128.TrailingZeroCount(v);
|
||||
v >>= zv;
|
||||
if (u > v)
|
||||
{
|
||||
(u, v) = (v, u);
|
||||
}
|
||||
|
||||
v -= u;
|
||||
}
|
||||
while (!v.IsZero);
|
||||
|
||||
return u << shift;
|
||||
}
|
||||
|
||||
public readonly record struct BinaryGcdInput(GpuUInt128 Left, GpuUInt128 Right, string Name)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128Montgomery64Benchmarks
|
||||
{
|
||||
private static readonly MontgomeryInput[] Inputs = new MontgomeryInput[]
|
||||
{
|
||||
Create(0x0000_0000_0000_0065UL, 0x0000_0000_0000_0037UL, 0x0000_0000_0000_007FUL, "TinyOperands"),
|
||||
Create(0x7FFF_FFFF_FFFF_FFABUL, 0x7FFF_FFFF_FFFF_FFC5UL, 0xFFFF_FFFF_FFFF_FFC5UL, "NearModulus"),
|
||||
Create(0x0123_4567_89AB_CDEFUL, 0x0FED_CBA9_8765_4321UL, 0xFFFF_FFFF_FFFF_FF4FUL, "MixedMagnitude"),
|
||||
Create(0xFFFF_FFFF_FFFF_FF01UL, 0xFFFF_FFFF_FFFF_FDCBUL, 0xFFFF_FFFF_FFFF_FFCFUL, "DenseOperands"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public MontgomeryInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<MontgomeryInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ExtensionMontgomeryMultiply()
|
||||
{
|
||||
return Input.Left.MontgomeryMultiply(Input.Right, Input.Modulus, Input.NPrime);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ulong GpuStructMontgomeryMultiply()
|
||||
{
|
||||
GpuUInt128 state = new(Input.Left);
|
||||
return state.MulModMontgomery64(Input.Right, Input.Modulus, Input.NPrime, Input.R2);
|
||||
}
|
||||
|
||||
private static MontgomeryInput Create(ulong left, ulong right, ulong modulus, string name)
|
||||
{
|
||||
ulong adjustedModulus = (modulus & 1UL) == 0UL ? modulus | 1UL : modulus;
|
||||
ulong nPrime = ComputeMontgomeryNPrime(adjustedModulus);
|
||||
ulong r2 = ComputeMontgomeryR2(adjustedModulus);
|
||||
return new MontgomeryInput(left, right, adjustedModulus, nPrime, r2, name);
|
||||
}
|
||||
|
||||
private static ulong ComputeMontgomeryR2(ulong modulus)
|
||||
{
|
||||
UInt128 r = UInt128.One << 64;
|
||||
return (ulong)((r * r) % modulus);
|
||||
}
|
||||
|
||||
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||||
{
|
||||
ulong inv = modulus;
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
|
||||
public readonly record struct MontgomeryInput(
|
||||
ulong Left,
|
||||
ulong Right,
|
||||
ulong Modulus,
|
||||
ulong NPrime,
|
||||
ulong R2,
|
||||
string Name)
|
||||
{
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128Mul64Benchmarks
|
||||
{
|
||||
private static readonly Mul64Input[] Inputs = new Mul64Input[]
|
||||
{
|
||||
new(new GpuUInt128(0UL, 1UL), new GpuUInt128(0UL, 1UL), "TinyOperands"),
|
||||
new(new GpuUInt128(0UL, ulong.MaxValue), new GpuUInt128(0UL, ulong.MaxValue), "LowWordMax"),
|
||||
new(new GpuUInt128(1UL, 0UL), new GpuUInt128(0UL, ulong.MaxValue), "HighByLow"),
|
||||
new(new GpuUInt128(ulong.MaxValue, ulong.MaxValue), new GpuUInt128(ulong.MaxValue, ulong.MaxValue), "AllBitsSet"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public Mul64Input Input { get; set; }
|
||||
|
||||
public static IEnumerable<Mul64Input> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 HighProductMaterialisedInLocal()
|
||||
{
|
||||
GpuUInt128 value = Input.Multiplicand;
|
||||
value.Mul64(Input.Multiplier);
|
||||
return value;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public GpuUInt128 HighProductInline()
|
||||
{
|
||||
GpuUInt128 value = Input.Multiplicand;
|
||||
Mul64Inline(ref value, Input.Multiplier);
|
||||
return value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Mul64Inline(ref GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
ulong operand = left.Low;
|
||||
left.Low = operand * right.Low;
|
||||
left.High = operand * right.High + GpuUInt128.MulHigh(operand, right.Low);
|
||||
}
|
||||
|
||||
public readonly record struct Mul64Input(GpuUInt128 Multiplicand, GpuUInt128 Multiplier, string Name)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128MulModBenchmarks
|
||||
{
|
||||
private static readonly MulModInput[] Inputs = new MulModInput[]
|
||||
{
|
||||
new(
|
||||
new GpuUInt128(0UL, 5UL),
|
||||
new GpuUInt128(0UL, 7UL),
|
||||
new GpuUInt128(0UL, 97UL),
|
||||
"TinyOperands"),
|
||||
new(
|
||||
new GpuUInt128(0UL, ulong.MaxValue - 3UL),
|
||||
new GpuUInt128(0UL, ulong.MaxValue - 7UL),
|
||||
new GpuUInt128(0UL, ulong.MaxValue - 11UL),
|
||||
"LowWordHeavy"),
|
||||
new(
|
||||
new GpuUInt128(0x1234_5678_9ABC_DEF0UL, 0x0FED_CBA9_8765_4321UL),
|
||||
new GpuUInt128(0x0ACE_BDF0_1357_9BDFUL, 0x0246_8ACE_ECA8_6421UL),
|
||||
new GpuUInt128(0x1FFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFF7UL),
|
||||
"HighWordModulus"),
|
||||
new(
|
||||
new GpuUInt128(0x8000_0000_0000_0000UL, 0x0000_0000_0000_0001UL),
|
||||
new GpuUInt128(0x7FFF_FFFF_FFFF_FFFFUL, 0x8000_0000_0000_0000UL),
|
||||
new GpuUInt128(0x7FFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFF1UL),
|
||||
"MixedMagnitude"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public MulModInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<MulModInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 InPlaceMulMod()
|
||||
{
|
||||
GpuUInt128 value = Input.Left;
|
||||
value.MulMod(Input.Right, Input.Modulus);
|
||||
return value;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public GpuUInt128 AllocatePerIteration()
|
||||
{
|
||||
return MulModInline(Input.Left, Input.Right, Input.Modulus);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static GpuUInt128 MulModInline(GpuUInt128 left, GpuUInt128 right, GpuUInt128 modulus)
|
||||
{
|
||||
GpuUInt128 a = left;
|
||||
GpuUInt128 b = right;
|
||||
GpuUInt128 result = new();
|
||||
|
||||
while (!b.IsZero)
|
||||
{
|
||||
if ((b.Low & 1UL) != 0UL)
|
||||
{
|
||||
result.Add(a);
|
||||
if (result.CompareTo(modulus) >= 0)
|
||||
{
|
||||
result.Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
a <<= 1;
|
||||
if (a.CompareTo(modulus) >= 0)
|
||||
{
|
||||
a.Sub(modulus);
|
||||
}
|
||||
|
||||
if (b.High == 0UL)
|
||||
{
|
||||
b = new GpuUInt128(b.Low >> 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong low = (b.Low >> 1) | (b.High << 63);
|
||||
ulong high = b.High >> 1;
|
||||
b = new GpuUInt128(high, low);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public readonly record struct MulModInput(GpuUInt128 Left, GpuUInt128 Right, GpuUInt128 Modulus, string Name)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128MulModByLimbBenchmarks
|
||||
{
|
||||
private static readonly MulModByLimbInput[] Inputs = new MulModByLimbInput[]
|
||||
{
|
||||
new(
|
||||
new GpuUInt128(0UL, 17UL),
|
||||
new GpuUInt128(0UL, 19UL),
|
||||
new GpuUInt128(0x0000_0000_0000_0001UL, 0x0000_0000_0000_00FFUL),
|
||||
"TinyOperands"),
|
||||
new(
|
||||
new GpuUInt128(0x0123_4567_89AB_CDEFUL, 0xFEDC_BA98_7654_3210UL),
|
||||
new GpuUInt128(0x0F1E_2D3C_4B5A_6978UL, 0x8776_6554_4332_2110UL),
|
||||
new GpuUInt128(0x0FFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFC3UL),
|
||||
"MixedMagnitude"),
|
||||
new(
|
||||
new GpuUInt128(0x8000_0000_0000_0000UL, 0x0000_0000_0000_0001UL),
|
||||
new GpuUInt128(0x7FFF_FFFF_FFFF_FFFFUL, 0x8000_0000_0000_0000UL),
|
||||
new GpuUInt128(0x7FFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFF1UL),
|
||||
"HighWordDominant"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public MulModByLimbInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<MulModByLimbInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 LegacyAllocating()
|
||||
{
|
||||
return MulModByLimbLegacy(Input.Left, Input.Right, Input.Modulus);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public GpuUInt128 InPlaceReduction()
|
||||
{
|
||||
GpuUInt128 value = Input.Left;
|
||||
value.MulModByLimb(Input.Right, Input.Modulus);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static GpuUInt128 MulModByLimbLegacy(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 <<= 64;
|
||||
remainder = new GpuUInt128(remainder.High, limb);
|
||||
while (remainder.CompareTo(modulus) >= 0)
|
||||
{
|
||||
remainder.Sub(modulus);
|
||||
}
|
||||
|
||||
limb = p0;
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
private static (ulong P3, ulong P2, ulong P1, ulong P0) MultiplyFullLegacy(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
var (h0, l0) = Mul64Legacy(left.Low, right.Low);
|
||||
var (h1, l1) = Mul64Legacy(left.Low, right.High);
|
||||
var (h2, l2) = Mul64Legacy(left.High, right.Low);
|
||||
var (h3, l3) = Mul64Legacy(left.High, right.High);
|
||||
|
||||
ulong carry = 0UL;
|
||||
ulong sum0 = l0;
|
||||
ulong sum1 = h0;
|
||||
|
||||
sum1 += l1;
|
||||
if (sum1 < l1)
|
||||
{
|
||||
carry++;
|
||||
}
|
||||
|
||||
sum1 += l2;
|
||||
if (sum1 < l2)
|
||||
{
|
||||
carry++;
|
||||
}
|
||||
|
||||
ulong sum2 = h1 + h2;
|
||||
ulong carry2 = sum2 < h2 ? 1UL : 0UL;
|
||||
sum2 += l3;
|
||||
if (sum2 < l3)
|
||||
{
|
||||
carry2++;
|
||||
}
|
||||
|
||||
sum2 += carry;
|
||||
if (sum2 < carry)
|
||||
{
|
||||
carry2++;
|
||||
}
|
||||
|
||||
ulong p1 = sum1;
|
||||
ulong p2 = sum2;
|
||||
ulong p3 = h3 + carry2;
|
||||
|
||||
return (p3, p2, p1, sum0);
|
||||
}
|
||||
|
||||
private static (ulong High, ulong Low) Mul64Legacy(ulong left, ulong right)
|
||||
{
|
||||
ulong a0 = (uint)left;
|
||||
ulong a1 = left >> 32;
|
||||
ulong b0 = (uint)right;
|
||||
ulong b1 = right >> 32;
|
||||
|
||||
ulong lo = a0 * b0;
|
||||
ulong mid1 = a1 * b0;
|
||||
b0 = a0 * b1;
|
||||
b1 *= a1;
|
||||
|
||||
a0 = (lo >> 32) + (uint)mid1 + (uint)b0;
|
||||
a1 = (lo & 0xFFFF_FFFFUL) | (a0 << 32);
|
||||
b1 += (mid1 >> 32) + (b0 >> 32) + (a0 >> 32);
|
||||
|
||||
return (b1, a1);
|
||||
}
|
||||
|
||||
public readonly record struct MulModByLimbInput(GpuUInt128 Left, GpuUInt128 Right, GpuUInt128 Modulus, string Name)
|
||||
{
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128NativeModuloBenchmarks
|
||||
{
|
||||
private static readonly NativeModuloInput[] Inputs = new NativeModuloInput[]
|
||||
{
|
||||
new(0x0000_0000_0000_0003UL, 0x0000_0000_0000_0005UL, 0x0000_0000_0000_007FUL, "TinyOperands"),
|
||||
new(0x7FFF_FFFF_FFFF_FFC3UL, 0x7FFF_FFFF_FFFF_FF21UL, 0x7FFF_FFFF_FFFF_FFC5UL, "NearModulus"),
|
||||
new(0xFFFF_FFFF_FFFF_FFFBUL, 0xFFFF_FFFF_FFFF_FFCFUL, 0xFFFF_FFFF_FFFF_FFF5UL, "DenseOperands"),
|
||||
new(0x0123_4567_89AB_CDEFUL, 0x0FED_CBA9_8765_4321UL, 0x1FFF_FFFF_FFFF_FFFBUL, "MixedMagnitude"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public NativeModuloInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<NativeModuloInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ImmediateModulo()
|
||||
{
|
||||
GpuUInt128 state = new(Input.Left % Input.Modulus);
|
||||
return state.MulMod(Input.Right, Input.Modulus);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ulong DeferredNativeModulo()
|
||||
{
|
||||
GpuUInt128 state = new(Input.Left);
|
||||
return state.MulModWithNativeModulo(Input.Right, Input.Modulus);
|
||||
}
|
||||
|
||||
public readonly record struct NativeModuloInput(ulong Left, ulong Right, ulong Modulus, string Name)
|
||||
{
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128ScalarMulModBenchmarks
|
||||
{
|
||||
private static readonly ScalarMulModInput[] Inputs = new ScalarMulModInput[]
|
||||
{
|
||||
new(
|
||||
new GpuUInt128(0x0000_0000_0000_0000UL, 0x0000_0000_0000_0011UL),
|
||||
0x0000_0000_0000_0013UL,
|
||||
new GpuUInt128(0x0000_0000_0000_0001UL, 0x0000_0000_0000_00FFUL),
|
||||
"TinyOperands"),
|
||||
new(
|
||||
new GpuUInt128(0x0123_4567_89AB_CDEFUL, 0xFEDC_BA98_7654_3210UL),
|
||||
0x0F1E_2D3C_4B5A_6978UL,
|
||||
new GpuUInt128(0x0FFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFC3UL),
|
||||
"MixedMagnitude"),
|
||||
new(
|
||||
new GpuUInt128(0x8000_0000_0000_0000UL, 0x0000_0000_0000_0001UL),
|
||||
0x7FFF_FFFF_FFFF_FFFBUL,
|
||||
new GpuUInt128(0x7FFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFF1UL),
|
||||
"HighWordDominant"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public ScalarMulModInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<ScalarMulModInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 StructAllocating()
|
||||
{
|
||||
GpuUInt128 value = Input.Left;
|
||||
value.MulMod(new GpuUInt128(Input.Right), Input.Modulus);
|
||||
return value;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public GpuUInt128 SpecializedScalar()
|
||||
{
|
||||
GpuUInt128 value = Input.Left;
|
||||
value.MulMod(Input.Right, Input.Modulus);
|
||||
return value;
|
||||
}
|
||||
|
||||
public readonly record struct ScalarMulModInput(GpuUInt128 Left, ulong Right, GpuUInt128 Modulus, string Name)
|
||||
{
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80)]
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128SubModScalarBenchmarks
|
||||
{
|
||||
private static readonly SubModInput[] Inputs = new SubModInput[]
|
||||
{
|
||||
new(new GpuUInt128(0UL, 3UL), 5UL, new GpuUInt128(0UL, 11UL), "UnderflowAddsModulus"),
|
||||
new(new GpuUInt128(0UL, ulong.MaxValue - 3UL), 7UL, new GpuUInt128(0UL, ulong.MaxValue - 11UL), "LargeLowWord"),
|
||||
new(new GpuUInt128(17UL, 2UL), 1UL, new GpuUInt128(3UL, 7UL), "HighWordBorrow"),
|
||||
};
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public SubModInput Input { get; set; }
|
||||
|
||||
public static IEnumerable<SubModInput> GetInputs() => Inputs;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 CurrentInPlace()
|
||||
{
|
||||
GpuUInt128 value = Input.Left;
|
||||
value.SubMod(Input.Scalar, Input.Modulus);
|
||||
return value;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public GpuUInt128 LegacyTemporaries()
|
||||
{
|
||||
GpuUInt128 value = Input.Left;
|
||||
LegacySubMod(ref value, Input.Scalar, Input.Modulus);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void LegacySubMod(ref GpuUInt128 value, ulong scalar, GpuUInt128 modulus)
|
||||
{
|
||||
ulong low;
|
||||
ulong high;
|
||||
if (value.High == 0UL && value.Low < scalar)
|
||||
{
|
||||
low = value.Low + modulus.Low;
|
||||
ulong carry = low < value.Low ? 1UL : 0UL;
|
||||
high = value.High + modulus.High + carry;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = value.High;
|
||||
low = value.Low;
|
||||
}
|
||||
|
||||
ulong borrow = low < scalar ? 1UL : 0UL;
|
||||
value.High = high - borrow;
|
||||
value.Low = low - scalar;
|
||||
}
|
||||
|
||||
public readonly record struct SubModInput(GpuUInt128 Left, ulong Scalar, GpuUInt128 Modulus, string Name)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,7 @@ public class MulMod64Benchmarks
|
||||
public ulong GpuCompatibleMulModExtension()
|
||||
{
|
||||
GpuUInt128 gpuUInt128 = new(Input.Left);
|
||||
gpuUInt128.MulMod(Input.Right, Input.Modulus);
|
||||
return gpuUInt128.Low;
|
||||
return gpuUInt128.MulMod(Input.Right, Input.Modulus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,8 +73,7 @@ public class MulMod64Benchmarks
|
||||
public ulong GpuCompatibleMulModSimplifiedExtension()
|
||||
{
|
||||
GpuUInt128 gpuUInt128 = new(Input.Left);
|
||||
gpuUInt128.MulModSimplified(Input.Right, Input.Modulus);
|
||||
return gpuUInt128.Low;
|
||||
return gpuUInt128.MulModSimplified(Input.Right, Input.Modulus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in new issue
Block a user