Merge branch 'codex/analyze-gc-instances-in-checkdivisors-method' of https://github.com/mlemanczyk/even-perfect-numbers-scanner into codex/analyze-gc-instances-in-checkdivisors-method
This commit is contained in:
commit
44f65b1c8f
45 files changed
+2428
-2666
No files matched your search
@@ -9,12 +9,10 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
||||
public class GpuModularArithmeticBenchmarks
|
||||
{
|
||||
private const int MaxBatchSize = 512;
|
||||
private readonly ulong[] _exponents = new ulong[MaxBatchSize];
|
||||
private readonly ulong[] _moduli = new ulong[MaxBatchSize];
|
||||
private readonly ulong[] _mersenneModuli = new ulong[MaxBatchSize];
|
||||
private readonly int[] _mersenneBitWidths = new int[MaxBatchSize];
|
||||
|
||||
private readonly Random _random = new(7);
|
||||
private ulong[] _exponents = Array.Empty<ulong>();
|
||||
private ulong[] _moduli = Array.Empty<ulong>();
|
||||
private ulong[] _mersenneModuli = Array.Empty<ulong>();
|
||||
private int[] _mersenneBitWidths = Array.Empty<int>();
|
||||
|
||||
[Params(32, 256)]
|
||||
public int BatchSize { get; set; }
|
||||
@@ -22,14 +20,69 @@ public class GpuModularArithmeticBenchmarks
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
for (int i = 0; i < MaxBatchSize; i++)
|
||||
{
|
||||
_exponents[i] = NextExponent();
|
||||
_moduli[i] = NextOddModulus();
|
||||
ModularArithmeticSampleData data = ModularArithmeticSampleCache.Instance;
|
||||
_exponents = data.Exponents;
|
||||
_moduli = data.Moduli;
|
||||
_mersenneModuli = data.MersenneModuli;
|
||||
_mersenneBitWidths = data.MersenneBitWidths;
|
||||
}
|
||||
|
||||
int bits = _random.Next(8, 62);
|
||||
_mersenneBitWidths[i] = bits;
|
||||
_mersenneModuli[i] = (1UL << bits) - 1UL;
|
||||
private sealed class ModularArithmeticSampleData
|
||||
{
|
||||
public ModularArithmeticSampleData(ulong[] exponents, ulong[] moduli, ulong[] mersenneModuli, int[] mersenneBitWidths)
|
||||
{
|
||||
Exponents = exponents;
|
||||
Moduli = moduli;
|
||||
MersenneModuli = mersenneModuli;
|
||||
MersenneBitWidths = mersenneBitWidths;
|
||||
}
|
||||
|
||||
public ulong[] Exponents { get; }
|
||||
public ulong[] Moduli { get; }
|
||||
public ulong[] MersenneModuli { get; }
|
||||
public int[] MersenneBitWidths { get; }
|
||||
}
|
||||
|
||||
private static class ModularArithmeticSampleCache
|
||||
{
|
||||
private static readonly Lazy<ModularArithmeticSampleData> Cache = new(Create);
|
||||
|
||||
public static ModularArithmeticSampleData Instance => Cache.Value;
|
||||
|
||||
private static ModularArithmeticSampleData Create()
|
||||
{
|
||||
Random random = new(7);
|
||||
var exponents = new ulong[MaxBatchSize];
|
||||
var moduli = new ulong[MaxBatchSize];
|
||||
var mersenneModuli = new ulong[MaxBatchSize];
|
||||
var mersenneBitWidths = new int[MaxBatchSize];
|
||||
|
||||
for (int i = 0; i < MaxBatchSize; i++)
|
||||
{
|
||||
exponents[i] = NextExponent(random);
|
||||
moduli[i] = NextOddModulus(random);
|
||||
int bits = random.Next(8, 62);
|
||||
mersenneBitWidths[i] = bits;
|
||||
mersenneModuli[i] = (1UL << bits) - 1UL;
|
||||
}
|
||||
|
||||
return new ModularArithmeticSampleData(exponents, moduli, mersenneModuli, mersenneBitWidths);
|
||||
}
|
||||
|
||||
private static ulong NextExponent(Random random)
|
||||
{
|
||||
return (ulong)random.Next(24, 63);
|
||||
}
|
||||
|
||||
private static ulong NextOddModulus(Random random)
|
||||
{
|
||||
ulong value = (ulong)random.NextInt64(5L, long.MaxValue);
|
||||
if ((value & 1UL) == 0UL)
|
||||
{
|
||||
value++;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,22 +122,6 @@ public class GpuModularArithmeticBenchmarks
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private ulong NextExponent()
|
||||
{
|
||||
return (ulong)_random.Next(24, 63);
|
||||
}
|
||||
|
||||
private ulong NextOddModulus()
|
||||
{
|
||||
ulong value = (ulong)_random.NextInt64(5L, long.MaxValue);
|
||||
if ((value & 1UL) == 0UL)
|
||||
{
|
||||
value++;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static ulong Pow2ModBaseline(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
||||
[MemoryDiagnoser]
|
||||
public class GpuUInt128MulModBenchmarks
|
||||
{
|
||||
private static readonly MulModInput[] Inputs =
|
||||
private static readonly GpuMulModCase[] GeneralCases =
|
||||
[
|
||||
new(
|
||||
new GpuUInt128(0UL, 5UL),
|
||||
@@ -33,10 +33,28 @@ public class GpuUInt128MulModBenchmarks
|
||||
"MixedMagnitude"),
|
||||
];
|
||||
|
||||
[ParamsSource(nameof(GetInputs))]
|
||||
public MulModInput Input { get; set; }
|
||||
private static readonly GpuMulModCase[] ByLimbCases =
|
||||
[
|
||||
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"),
|
||||
];
|
||||
|
||||
public static IEnumerable<MulModInput> GetInputs() => Inputs;
|
||||
public static IEnumerable<GpuMulModCase> GetGeneralCases() => GeneralCases;
|
||||
|
||||
public static IEnumerable<GpuMulModCase> GetByLimbCases() => ByLimbCases;
|
||||
|
||||
/// <summary>
|
||||
/// In-place multiply-modulo; great for low-word-heavy operands (6.88 ns) and tiny pairs (7.15 ns) but notably slower on
|
||||
@@ -46,11 +64,12 @@ public class GpuUInt128MulModBenchmarks
|
||||
/// Observed means: HighWordModulus 192.794 ns (1.00×), LowWordHeavy 6.883 ns, MixedMagnitude 201.468 ns, TinyOperands 7.146 ns.
|
||||
/// </remarks>
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 InPlaceMulMod()
|
||||
[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);
|
||||
GpuUInt128 value = input.Left;
|
||||
value.MulMod(input.Right, input.Modulus);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -62,9 +81,41 @@ public class GpuUInt128MulModBenchmarks
|
||||
/// Observed means: HighWordModulus 184.322 ns (0.96×), LowWordHeavy 116.993 ns, MixedMagnitude 138.684 ns, TinyOperands 4.659 ns.
|
||||
/// </remarks>
|
||||
[Benchmark]
|
||||
public GpuUInt128 AllocatePerIteration()
|
||||
[ArgumentsSource(nameof(GetGeneralCases))]
|
||||
public GpuUInt128 AllocatePerIteration(GpuMulModCase input)
|
||||
{
|
||||
return MulModInline(Input.Left, Input.Right, Input.Modulus);
|
||||
return MulModInline(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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Observed means: HighWordDominant 12.69 ns (1.00×), MixedMagnitude 16.26 ns, TinyOperands 10.74 ns.
|
||||
/// </remarks>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetByLimbCases))]
|
||||
public GpuUInt128 LegacyAllocating(GpuMulModCase input)
|
||||
{
|
||||
return MulModByLimbLegacy(input.Left, input.Right, input.Modulus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In-place reduction variant; avoids allocations but costs 12.9–25.1 ns, trailing the legacy path by 20–55% depending on the
|
||||
/// operand mix.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Observed means: HighWordDominant 17.88 ns (1.41×), MixedMagnitude 25.14 ns, TinyOperands 12.92 ns.
|
||||
/// </remarks>
|
||||
[Benchmark]
|
||||
[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;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -107,7 +158,96 @@ public class GpuUInt128MulModBenchmarks
|
||||
return result;
|
||||
}
|
||||
|
||||
public readonly record struct MulModInput(GpuUInt128 Left, GpuUInt128 Right, GpuUInt128 Modulus, string Name)
|
||||
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 GpuMulModCase(GpuUInt128 Left, GpuUInt128 Right, GpuUInt128 Modulus, string Name)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Observed means: HighWordDominant 12.69 ns (1.00×), MixedMagnitude 16.26 ns, TinyOperands 10.74 ns.
|
||||
/// </remarks>
|
||||
[Benchmark(Baseline = true)]
|
||||
public GpuUInt128 LegacyAllocating()
|
||||
{
|
||||
return MulModByLimbLegacy(Input.Left, Input.Right, Input.Modulus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In-place reduction variant; avoids allocations but costs 12.9–25.1 ns, trailing the legacy path by 20–55% depending on the
|
||||
/// operand mix.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Observed means: HighWordDominant 17.88 ns (1.41×), MixedMagnitude 25.14 ns, TinyOperands 12.92 ns.
|
||||
/// </remarks>
|
||||
[Benchmark]
|
||||
public GpuUInt128 InPlaceReduction()
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
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,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod10Benchmarks
|
||||
{
|
||||
private static readonly ModBenchmarkCase[] Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 10U, "UInt32:10"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt64, 3UL, "UInt64:3"),
|
||||
new(ModNumericType.UInt64, 8191UL, "UInt64:8191"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt128, 3UL, "UInt128:3"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
];
|
||||
|
||||
private static readonly ModBenchmarkCase[] NonUInt128Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 10U, "UInt32:10"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt64, 3UL, "UInt64:3"),
|
||||
new(ModNumericType.UInt64, 8191UL, "UInt64:8191"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
];
|
||||
|
||||
private static readonly ModBenchmarkCase[] UInt128Cases =
|
||||
[
|
||||
new(ModNumericType.UInt128, 3UL, "UInt128:3"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
];
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetCases() => Cases;
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetNonUInt128Cases() => NonUInt128Cases;
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetUInt128Cases() => UInt128Cases;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint Mod10(uint value) => value - (uint)((value * UIntExtensions.Mod5Mask) >> 35) * 10U;
|
||||
|
||||
private static ulong ModWithHighLowModuloHelper(UInt128 value128)
|
||||
{
|
||||
ulong value = (ulong)value128;
|
||||
return ((value % 10UL) + ((value >> 64) % 10UL) * 6UL) % 10UL;
|
||||
}
|
||||
|
||||
private static ulong ModWithLoopModuloHelper(UInt128 value)
|
||||
{
|
||||
UInt128 zero = UInt128.Zero;
|
||||
if (value == zero)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong result = (ulong)value;
|
||||
value >>= 64;
|
||||
|
||||
while (value != zero)
|
||||
{
|
||||
result = (result + (ulong)value * 6UL) % 10UL;
|
||||
value >>= 64;
|
||||
}
|
||||
|
||||
return result % 10UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `% 10` baseline; ranged from 0.04-0.32 ns on <c>uint</c>, ~0.26 ns on <c>ulong</c>, and 3.25-3.38 ns on
|
||||
/// <see cref="UInt128"/> inputs across the consolidated dataset.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ModuloOperator(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => (uint)data.Value % 10U,
|
||||
ModNumericType.UInt64 => (ulong)data.Value % 10UL,
|
||||
ModNumericType.UInt128 => (ulong)(data.Value % 10UL),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mask-based helpers for <c>uint</c> and <c>ulong</c>; they trail the raw `%` on 64-bit inputs and only match it on the
|
||||
/// smaller width.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetNonUInt128Cases))]
|
||||
public ulong ExtensionMethod(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => Mod10((uint)data.Value),
|
||||
ModNumericType.UInt64 => ((ulong)data.Value).Mod10(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// High/low folding helper for <see cref="UInt128"/>; measured 1.78-1.82 ns in the original suite, about 1.9× faster than `%`.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetUInt128Cases))]
|
||||
public ulong ModWithHighLowModulo(ModBenchmarkCase data)
|
||||
{
|
||||
return ModWithHighLowModuloHelper(data.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loop-based folding helper for <see cref="UInt128"/>; lands between the high/low helper and the raw modulo at 2.20-2.27 ns.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetUInt128Cases))]
|
||||
public ulong ModWithLoopModulo(ModBenchmarkCase data)
|
||||
{
|
||||
return ModWithLoopModuloHelper(data.Value);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class Mod10UInt128Benchmarks
|
||||
{
|
||||
[Params(3UL, 8191UL, 131071UL, 2147483647UL)]
|
||||
public UInt128 Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Direct `% 10` using the <see cref="UInt128"/> operator; lands between 3.25 ns and 3.38 ns (3.378 ns at value 3, 3.269 ns at
|
||||
/// 8191, 3.248 ns at 131071, 3.281 ns at 2147483647) and serves as the baseline.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public UInt128 StandardModulo()
|
||||
{
|
||||
return Value % 10UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the value into high/low halves and folds with the 2^64 ≡ 6 (mod 10) rule; fastest option at 1.78-1.82 ns (1.804 ns at
|
||||
/// value 3, 1.798 ns at 8191, 1.817 ns at 131071, 1.779 ns at 2147483647), beating the baseline by ~1.9x.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ModWithHighLowModulo()
|
||||
{
|
||||
return ModWithHighLowModuloHelper(Value);
|
||||
}
|
||||
|
||||
private static ulong ModWithHighLowModuloHelper(UInt128 value128)
|
||||
{
|
||||
ulong value = (ulong)value128;
|
||||
return ((value % 10UL) + ((value >> 64) % 10UL) * 6UL) % 10UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loop-based folding that repeatedly applies the 2^64 ≡ 6 rule; measures 2.20-2.27 ns (2.204 ns at value 3, 2.234 ns at 8191,
|
||||
/// 2.268 ns at 131071, 2.216 ns at 2147483647), situating it between the high/low helper and the raw modulo.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ModWithLoopModulo()
|
||||
{
|
||||
return ModWithLoopModuloHelper(Value);
|
||||
}
|
||||
|
||||
private static ulong ModWithLoopModuloHelper(UInt128 value)
|
||||
{
|
||||
UInt128 zero = UInt128.Zero;
|
||||
if (value == zero)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong result = (ulong)value;
|
||||
value >>= 64;
|
||||
|
||||
while (value != zero)
|
||||
{
|
||||
result = (result + (ulong)value * 6UL) % 10UL;
|
||||
value >>= 64;
|
||||
}
|
||||
|
||||
return result % 10UL;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod10UIntBenchmarks
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Mod10(uint value) => value - (uint)((value * UIntExtensions.Mod5Mask) >> 35) * 10U;
|
||||
|
||||
[Params(10U, 2047U, 65535U, 2147483647U)]
|
||||
public uint Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Direct `% 10` on <see cref="uint"/>; BenchmarkDotNet flagged near-zero timings, but the measured means were 0.0407 ns at
|
||||
/// value 10, 0.3224 ns at 2047, 0.2382 ns at 65535, and 0.0555 ns at 2147483647.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public uint ModuloOperator()
|
||||
{
|
||||
return Value % 10U;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized extension method that folds with a precomputed mask; still costs 0.77 ns on the tiny value 10 but drops to
|
||||
/// 0.0448 ns at 2047, 0.0405 ns at 65535, and 0.0372 ns at 2147483647 (roughly 6-9x faster where the baseline exceeded the
|
||||
/// empty-loop noise floor).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public uint ExtensionMethod()
|
||||
{
|
||||
return Mod10(Value);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class Mod10ULongBenchmarks
|
||||
{
|
||||
[Params(3UL, 8191UL, 131071UL, 2147483647UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scalar `% 10` baseline; consistently measured around 0.26 ns (0.2591 ns at value 3, 0.2597 ns at 8191, 0.2605 ns at 131071,
|
||||
/// 0.2598 ns at 2147483647).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong LegacyModulo()
|
||||
{
|
||||
return Value % 10UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper that applies the multiply-high mask; 0.73-0.75 ns across the suite (0.7503 ns at value 3, 0.7320 ns at
|
||||
/// 8191, 0.7346 ns at 131071, 0.7411 ns at 2147483647), roughly 2.8-2.9x slower than the raw modulo on `ulong`.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ModMethodModulo()
|
||||
{
|
||||
return Value.Mod10();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
@@ -6,33 +9,56 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
||||
[MemoryDiagnoser]
|
||||
public class Mod10_8_5_3Benchmarks
|
||||
{
|
||||
[Params(3UL, 8191UL, 131071UL, 2147483647UL)]
|
||||
public ulong Value { get; set; }
|
||||
public ulong Mod10R;
|
||||
public ulong Mod8R;
|
||||
public ulong Mod3R;
|
||||
public ulong Mod5R;
|
||||
|
||||
private static readonly ulong[] ValueCases =
|
||||
[
|
||||
3UL,
|
||||
6UL,
|
||||
128UL,
|
||||
256UL,
|
||||
2047UL,
|
||||
8191UL,
|
||||
65535UL,
|
||||
131071UL,
|
||||
2147483647UL,
|
||||
ulong.MaxValue - 1024UL,
|
||||
ulong.MaxValue - 511UL,
|
||||
ulong.MaxValue - 255UL,
|
||||
ulong.MaxValue - 127UL,
|
||||
ulong.MaxValue - 63UL,
|
||||
ulong.MaxValue - 31UL,
|
||||
ulong.MaxValue - 17UL,
|
||||
];
|
||||
|
||||
public static IEnumerable<ulong> GetValues() => ValueCases;
|
||||
|
||||
/// <summary>
|
||||
/// Baseline scalar modulo path; held steady between 1.13 ns and 1.14 ns across every value (1.138 ns at 3, 1.143 ns at 8191,
|
||||
/// 1.134 ns at 131071, 1.141 ns at 2147483647), making it the fastest option overall.
|
||||
/// Baseline scalar modulo path; previously 1.13-1.14 ns across the shared dataset and remains the reference point here.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModuloOperator()
|
||||
[ArgumentsSource(nameof(GetValues))]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModuloOperator(ulong value)
|
||||
{
|
||||
ulong value = Value;
|
||||
return (value % 10UL, value % 8UL, value % 3UL, value % 5UL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Legacy folding helper; useful for cross-checks but stays at 2.83-3.07 ns (2.834 ns at 3, 3.038 ns at 8191, 3.067 ns at
|
||||
/// 131071, 3.039 ns at 2147483647), roughly 2.5-2.7x slower than the baseline.
|
||||
/// Legacy folding helper; 2.83-3.07 ns in the original suite, now exercised on the combined dataset.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) LegacyModulo()
|
||||
[ArgumentsSource(nameof(GetValues))]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) LegacyModulo(ulong value)
|
||||
{
|
||||
ulong mod10;
|
||||
ulong mod8;
|
||||
ulong mod3;
|
||||
ulong mod5;
|
||||
UInt128 zero = UInt128.Zero;
|
||||
if (Value == zero)
|
||||
if (value == 0UL)
|
||||
{
|
||||
mod3 = 0UL;
|
||||
mod5 = 0UL;
|
||||
@@ -41,8 +67,8 @@ public class Mod10_8_5_3Benchmarks
|
||||
return (mod10, mod8, mod3, mod5);
|
||||
}
|
||||
|
||||
UInt128 high = Value >> 64;
|
||||
ulong result = Value;
|
||||
UInt128 high = value >> 64;
|
||||
ulong result = value;
|
||||
mod8 = result & 7UL;
|
||||
|
||||
ulong modRem = (result % 3UL) + ((ulong)high % 3UL);
|
||||
@@ -62,13 +88,86 @@ public class Mod10_8_5_3Benchmarks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method-based optimized helper; clocks in at 1.63 ns for value 3, 1.64 ns at 8191, 1.875 ns at 131071, and 2.164 ns at
|
||||
/// 2147483647 (1.43-1.90x the baseline) so it trails the raw `%` but remains 1.6x quicker than the legacy fold.
|
||||
/// Method-based optimized helper; 1.63-2.16 ns in the original benchmarks, roughly 1.4-1.9× slower than the raw modulo.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModMethodModulo()
|
||||
[ArgumentsSource(nameof(GetValues))]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModMethodModulo(ulong value)
|
||||
{
|
||||
Value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
|
||||
value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
|
||||
return (mod10, mod8, mod3, mod5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Legacy property-based helper; 2.35-2.46 ns at small values and up to 2.46 ns near <c>ulong.Max</c>.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetValues))]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod5, ulong Mod3) LegacyPropertyModulo(ulong value)
|
||||
{
|
||||
UInt128 wideValue = value;
|
||||
Mod10R = wideValue.Mod10();
|
||||
Mod8R = Mod8(wideValue);
|
||||
Mod5R = Mod5(wideValue);
|
||||
Mod3R = Mod3(wideValue);
|
||||
return (Mod10R, Mod8R, Mod5R, Mod3R);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method variant for the property helper; 2.79-4.34 ns in the original run, about 1.2-1.8× slower than the legacy property path.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetValues))]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModMethodPropertyModulo(ulong value)
|
||||
{
|
||||
value.Mod10_8_5_3(out Mod10R, out Mod8R, out Mod5R, out Mod3R);
|
||||
return (Mod10R, Mod8R, Mod3R, Mod5R);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Legacy step-per-bit helper; 11.9-13.4 ns in the original suite and remains the slowest option.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetValues))]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) LegacyStepsModulo(ulong value)
|
||||
{
|
||||
UInt128 wideValue = value;
|
||||
ulong mod10 = (((ulong)(wideValue % 10UL)) << 1) % 10UL;
|
||||
ulong mod8 = (((ulong)(wideValue & 7UL)) << 1) & 7UL;
|
||||
ulong mod5 = (((ulong)(wideValue % 5UL)) << 1) % 5UL;
|
||||
ulong mod3 = (((ulong)(wideValue % 3UL)) << 1) % 3UL;
|
||||
return (mod10, mod8, mod3, mod5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Step helper exposed via <see cref="UInt128Extensions.Mod10_8_5_3Steps"/>; 3.58-3.77 ns previously, delivering 3.2-3.6× speedups over the legacy step path.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetValues))]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModMethodStepsModulo(ulong value)
|
||||
{
|
||||
value.Mod10_8_5_3Steps(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
|
||||
return (mod10, mod8, mod3, mod5);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod8(UInt128 value) => (ulong)value & 7UL;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod3(UInt128 value)
|
||||
{
|
||||
ulong high = (ulong)(value >> 64);
|
||||
ulong low = (ulong)value;
|
||||
ulong rem = (low % 3UL) + (high % 3UL);
|
||||
return rem >= 3UL ? rem - 3UL : rem;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod5(UInt128 value)
|
||||
{
|
||||
ulong high = (ulong)(value >> 64);
|
||||
ulong low = (ulong)value;
|
||||
ulong rem = (low % 5UL) + (high % 5UL);
|
||||
return rem >= 5UL ? rem - 5UL : rem;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class Mod10_8_5_3PropertiesBenchmarks
|
||||
{
|
||||
public ulong Mod10R;
|
||||
public ulong Mod8R;
|
||||
public ulong Mod3R;
|
||||
public ulong Mod5R;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod8(UInt128 value) => (ulong)value & 7UL;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod3(UInt128 value)
|
||||
{
|
||||
ulong high = (ulong)(value >> 64);
|
||||
ulong low = (ulong)value;
|
||||
ulong rem = (low % 3UL) + (high % 3UL);
|
||||
return rem >= 3UL ? rem - 3UL : rem;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod5(UInt128 value)
|
||||
{
|
||||
ulong high = (ulong)(value >> 64);
|
||||
ulong low = (ulong)value;
|
||||
ulong rem = (low % 5UL) + (high % 5UL);
|
||||
return rem >= 5UL ? rem - 5UL : rem;
|
||||
}
|
||||
|
||||
[Params(
|
||||
3UL,
|
||||
6UL,
|
||||
128UL,
|
||||
256UL,
|
||||
2047UL,
|
||||
8191UL,
|
||||
65535UL,
|
||||
131071UL,
|
||||
2147483647UL,
|
||||
ulong.MaxValue - 1024UL,
|
||||
ulong.MaxValue - 511UL,
|
||||
ulong.MaxValue - 255UL,
|
||||
ulong.MaxValue - 127UL,
|
||||
ulong.MaxValue - 63UL,
|
||||
ulong.MaxValue - 31UL,
|
||||
ulong.MaxValue - 17UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Legacy property-based helper; stayed within 2.35-2.46 ns on the small inputs and 2.36-2.46 ns across the near-ulong.Max
|
||||
/// cases (e.g. 2.413 ns at value 3, 2.378 ns at 6, 2.458 ns at 2147483647, 2.371 ns near ulong.Max), keeping it in front.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod5, ulong Mod3) LegacyModulo()
|
||||
{
|
||||
UInt128 value = Value;
|
||||
Mod10R = value.Mod10();
|
||||
Mod8R = Mod8(value);
|
||||
Mod5R = Mod5(value);
|
||||
Mod3R = Mod3(value);
|
||||
return (Mod10R, Mod8R, Mod5R, Mod3R);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method variant that routes through <see cref="UInt128Extensions.Mod10_8_5_3"/>; ranged from 2.79 ns at value 3 to 4.34 ns on
|
||||
/// the largest operands (3.135 ns at 6, 3.033 ns at 2047, 3.441 ns at 2147483647, 4.289 ns near ulong.Max), roughly 1.16-1.83x
|
||||
/// slower than the legacy path.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModMethodModulo()
|
||||
{
|
||||
Value.Mod10_8_5_3(out Mod10R, out Mod8R, out Mod5R, out Mod3R);
|
||||
return (Mod10R, Mod8R, Mod3R, Mod5R);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class Mod10_8_5_3StepsBenchmarks
|
||||
{
|
||||
[Params(3UL, 8191UL, 131071UL, 2147483647UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Legacy step-per-bit helper; required 11.89-13.40 ns per call (12.052 ns at value 3, 11.891 ns at 8191, 13.401 ns at 131071,
|
||||
/// 12.114 ns at 2147483647), making it 3-4x slower than the streamlined method variant.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) LegacyModulo()
|
||||
{
|
||||
UInt128 value = Value;
|
||||
ulong mod10 = (((ulong)(value % 10UL)) << 1) % 10UL;
|
||||
ulong mod8 = (((ulong)(value & 7UL)) << 1) & 7UL;
|
||||
ulong mod5 = (((ulong)(value % 5UL)) << 1) % 5UL;
|
||||
ulong mod3 = (((ulong)(value % 3UL)) << 1) % 3UL;
|
||||
return (mod10, mod8, mod3, mod5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized step helper exposed on <see cref="UInt128Extensions.Mod10_8_5_3Steps"/>; trimmed execution to 3.58-3.77 ns
|
||||
/// (3.576 ns at value 3, 3.682 ns at 8191, 3.773 ns at 131071, 3.649 ns at 2147483647), giving a 3.2-3.6x speedup.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModMethodModulo()
|
||||
{
|
||||
Value.Mod10_8_5_3Steps(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3);
|
||||
return (mod10, mod8, mod3, mod5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod11Benchmarks
|
||||
{
|
||||
private static readonly ModBenchmarkCase[] Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 11U, "UInt32:11"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt64, 11UL, "UInt64:11"),
|
||||
new(ModNumericType.UInt64, 8191UL, "UInt64:8191"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt64, ulong.MaxValue - 127UL, "UInt64:Max-127"),
|
||||
new(ModNumericType.UInt128, 11UL, "UInt128:11"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
new(ModNumericType.UInt128, ulong.MaxValue - 255UL, "UInt128:Max-255"),
|
||||
];
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetCases() => Cases;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint Mod11(uint value)
|
||||
{
|
||||
uint remainder = 0U;
|
||||
while (value != 0U)
|
||||
{
|
||||
remainder += value & 1023U;
|
||||
value >>= 10;
|
||||
remainder -= 11U * (remainder / 11U);
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod11(ulong value)
|
||||
{
|
||||
ulong remainder = ((uint)value % 11U) + (((uint)(value >> 32) % 11U) << 2);
|
||||
while (remainder >= 11UL)
|
||||
{
|
||||
remainder -= 11UL;
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `% 11` baseline across the consolidated dataset; near-empty-loop costs on <c>uint</c>, 0.23-0.27 ns on <c>ulong</c>, and
|
||||
/// 2.85-2.87 ns on <see cref="UInt128"/> inputs.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ModuloOperator(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => (uint)data.Value % 11U,
|
||||
ModNumericType.UInt64 => (ulong)data.Value % 11UL,
|
||||
ModNumericType.UInt128 => (ulong)(data.Value % 11UL),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looping helpers; slower than `%` on <c>uint</c> and <c>ulong</c> but ~4.2× faster on <see cref="UInt128"/> thanks to digit
|
||||
/// folding.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ExtensionMethod(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => Mod11((uint)data.Value),
|
||||
ModNumericType.UInt64 => Mod11((ulong)data.Value),
|
||||
ModNumericType.UInt128 => data.Value.Mod11(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod11UInt128Benchmarks
|
||||
{
|
||||
[Params(11UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 255UL)]
|
||||
public UInt128 Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Direct `% 11` for <see cref="UInt128"/>; ran in ~2.85-2.87 ns on every dataset (2.8719 ns at value 11, 2.8715 ns at 8191,
|
||||
/// 2.8534 ns at 131071, 2.8716 ns at 2147483647, 2.8715 ns near ulong.Max).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return (ulong)(Value % 11UL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper that folds digits with lookup coefficients; 0.66-0.68 ns across the inputs (0.6721 ns at value 11, 0.6765 ns
|
||||
/// at 8191, 0.6760 ns at 131071, 0.6614 ns at 2147483647, 0.6661 ns near ulong.Max), about 4.2x faster than the baseline.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Value.Mod11();
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod11UIntBenchmarks
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Mod11(uint value)
|
||||
{
|
||||
uint remainder = 0U;
|
||||
while (value != 0U)
|
||||
{
|
||||
remainder += value & 1023U;
|
||||
value >>= 10;
|
||||
remainder -= 11U * (remainder / 11U);
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
[Params(11U, 2047U, 65535U, 2147483647U)]
|
||||
public uint Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 11` baseline; essentially free at this scale with means of 0.0299 ns (value 11), 0.0333 ns (2047), 0.0393 ns (65535), and
|
||||
/// 0.0367 ns (2147483647).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public uint ModuloOperator()
|
||||
{
|
||||
return Value % 11U;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looping extension method; useful for validation but slower across the board at 0.286 ns (value 11), 0.957 ns (2047),
|
||||
/// 0.922 ns (65535), and 3.170 ns (2147483647), i.e. 9-86x the direct modulo.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public uint ExtensionMethod()
|
||||
{
|
||||
return Mod11(Value);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod11ULongBenchmarks
|
||||
{
|
||||
[Params(11UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 127UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 11` baseline for `ulong`; timings hovered near 0.26-0.27 ns on the large inputs (0.2647 ns at 2147483647, 0.2709 ns near
|
||||
/// ulong.Max) with the smaller samples in the same 0.23-0.27 ns band.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return Value % 11UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup-based helper mirroring `UIntExtensions.Mod11`; costs 0.66-0.75 ns on 32-bit scale inputs and rises to 1.15 ns near
|
||||
/// ulong.Max, remaining 2.5-4.3x slower than the `%` baseline.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Mod11(Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Mod11(ulong value)
|
||||
{
|
||||
ulong remainder = ((uint)value % 11U) + ((uint)(value >> 32) % 11U) << 2;
|
||||
while (remainder >= 11UL)
|
||||
{
|
||||
remainder -= 11UL;
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod128Benchmarks
|
||||
{
|
||||
private static readonly ModBenchmarkCase[] Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 3U, "UInt32:3"),
|
||||
new(ModNumericType.UInt32, 5U, "UInt32:5"),
|
||||
new(ModNumericType.UInt32, 6U, "UInt32:6"),
|
||||
new(ModNumericType.UInt32, 8U, "UInt32:8"),
|
||||
new(ModNumericType.UInt32, 10U, "UInt32:10"),
|
||||
new(ModNumericType.UInt32, 11U, "UInt32:11"),
|
||||
new(ModNumericType.UInt32, 32U, "UInt32:32"),
|
||||
new(ModNumericType.UInt32, 64U, "UInt32:64"),
|
||||
new(ModNumericType.UInt32, 128U, "UInt32:128"),
|
||||
new(ModNumericType.UInt32, 256U, "UInt32:256"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 8191U, "UInt32:8191"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 131071U, "UInt32:131071"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 1024U, "UInt32:Max-1024"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 511U, "UInt32:Max-511"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 255U, "UInt32:Max-255"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 127U, "UInt32:Max-127"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 63U, "UInt32:Max-63"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 31U, "UInt32:Max-31"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 17U, "UInt32:Max-17"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue, "UInt32:Max"),
|
||||
new(ModNumericType.UInt64, 3UL, "UInt64:3"),
|
||||
new(ModNumericType.UInt64, 5UL, "UInt64:5"),
|
||||
new(ModNumericType.UInt64, 6UL, "UInt64:6"),
|
||||
new(ModNumericType.UInt64, 8UL, "UInt64:8"),
|
||||
new(ModNumericType.UInt64, 10UL, "UInt64:10"),
|
||||
new(ModNumericType.UInt64, 11UL, "UInt64:11"),
|
||||
new(ModNumericType.UInt64, 32UL, "UInt64:32"),
|
||||
new(ModNumericType.UInt64, 64UL, "UInt64:64"),
|
||||
new(ModNumericType.UInt64, 128UL, "UInt64:128"),
|
||||
new(ModNumericType.UInt64, 256UL, "UInt64:256"),
|
||||
new(ModNumericType.UInt64, 2047UL, "UInt64:2047"),
|
||||
new(ModNumericType.UInt64, 8191UL, "UInt64:8191"),
|
||||
new(ModNumericType.UInt64, 65535UL, "UInt64:65535"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt64, 4294966271UL, "UInt64:4294966271"),
|
||||
new(ModNumericType.UInt64, 4294966784UL, "UInt64:4294966784"),
|
||||
new(ModNumericType.UInt64, 4294967040UL, "UInt64:4294967040"),
|
||||
new(ModNumericType.UInt64, 4294967168UL, "UInt64:4294967168"),
|
||||
new(ModNumericType.UInt64, 4294967232UL, "UInt64:4294967232"),
|
||||
new(ModNumericType.UInt64, 4294967264UL, "UInt64:4294967264"),
|
||||
new(ModNumericType.UInt64, 4294967278UL, "UInt64:4294967278"),
|
||||
new(ModNumericType.UInt64, 4294967295UL, "UInt64:4294967295"),
|
||||
new(ModNumericType.UInt128, 128UL, "UInt128:128"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
new(ModNumericType.UInt128, ulong.MaxValue - 511UL, "UInt128:Max-511"),
|
||||
];
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetCases() => Cases;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint Mod128(uint value) => value & 127U;
|
||||
|
||||
/// <summary>
|
||||
/// `% 128` baseline; sits at 0.01-0.03 ns on <c>uint</c> and <c>ulong</c>, and 2.95-3.42 ns on <see cref="UInt128"/> inputs.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ModuloOperator(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => (uint)data.Value % 128U,
|
||||
ModNumericType.UInt64 => (ulong)data.Value % 128UL,
|
||||
ModNumericType.UInt128 => (ulong)(data.Value % 128UL),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask helpers; usually match `%` on 32-bit inputs, are faster on <c>ulong</c> with the exception of the 4,294,966,784
|
||||
/// outlier, and provide 150-1000× wins on <see cref="UInt128"/>.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ExtensionMethod(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => Mod128((uint)data.Value),
|
||||
ModNumericType.UInt64 => ((ulong)data.Value).Mod128(),
|
||||
ModNumericType.UInt128 => data.Value.Mod128(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod128UInt128Benchmarks
|
||||
{
|
||||
[Params(128UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 511UL)]
|
||||
public UInt128 Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 128` baseline for <see cref="UInt128"/>; measured 2.95-3.42 ns across the suite (3.423 ns at value 128, 3.025 ns at
|
||||
/// 8191, 2.950 ns at 131071, 2.948 ns at 2147483647, 2.967 ns near ulong.Max).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return (ulong)(Value % 128UL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask helper (`value & 127UL`); durations were indistinguishable from the empty-loop cost at 0.004-0.025 ns—roughly
|
||||
/// 150-1000x faster than the modulo baseline on every sample.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Value.Mod128();
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod128UIntBenchmarks
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Mod128(uint value) => value & 127U;
|
||||
|
||||
[Params(
|
||||
3U,
|
||||
5U,
|
||||
6U,
|
||||
8U,
|
||||
10U,
|
||||
11U,
|
||||
32U,
|
||||
64U,
|
||||
128U,
|
||||
256U,
|
||||
2047U,
|
||||
8191U,
|
||||
65535U,
|
||||
131071U,
|
||||
2147483647U,
|
||||
uint.MaxValue - 1024U,
|
||||
uint.MaxValue - 511U,
|
||||
uint.MaxValue - 255U,
|
||||
uint.MaxValue - 127U,
|
||||
uint.MaxValue - 63U,
|
||||
uint.MaxValue - 31U,
|
||||
uint.MaxValue - 17U,
|
||||
uint.MaxValue)]
|
||||
public uint Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 128` baseline; BenchmarkDotNet reported near-empty-loop costs with means from 0.010-0.026 ns across the domain (e.g.
|
||||
/// 0.0191 ns at value 3, 0.0136 ns at 5, 0.0170 ns at 32, 0.0108 ns at 256, 0.0138 ns at 2147483647).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public uint ModuloOperator()
|
||||
{
|
||||
return Value % 128U;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask helper that returns `value & 127`; generally matches the baseline at 0.006-0.023 ns (0.0163 ns at value 3, 0.0064 ns at
|
||||
/// 6, 0.0152 ns at 10, 0.0156 ns at 2147483647) but regresses on 64 due to the benchmark harness (0.9496 ns, 67x slower).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public uint ExtensionMethod()
|
||||
{
|
||||
return Mod128(Value);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod128ULongBenchmarks
|
||||
{
|
||||
[Params(
|
||||
3UL,
|
||||
5UL,
|
||||
6UL,
|
||||
8UL,
|
||||
10UL,
|
||||
11UL,
|
||||
32UL,
|
||||
64UL,
|
||||
128UL,
|
||||
256UL,
|
||||
2047UL,
|
||||
8191UL,
|
||||
65535UL,
|
||||
131071UL,
|
||||
2147483647UL,
|
||||
4294966271UL,
|
||||
4294966784UL,
|
||||
4294967040UL,
|
||||
4294967168UL,
|
||||
4294967232UL,
|
||||
4294967264UL,
|
||||
4294967278UL,
|
||||
4294967295UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 128` baseline; timings stayed below 0.03 ns on every dataset (0.0227 ns at value 3, 0.0108 ns at 256, 0.0251 ns at 2047,
|
||||
/// 0.0190 ns at 2147483647, 0.0165 ns at 4294966271).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return Value % 128UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask helper (`value & 127UL`); typically 0.006-0.015 ns (0.0113 ns at value 3, 0.0094 ns at 64, 0.0109 ns at 2147483647),
|
||||
/// but spikes to 0.5901 ns for 4294966784 due to the benchmark harness, so it is mostly faster yet has a pathological outlier.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Value.Mod128();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod3Benchmarks
|
||||
{
|
||||
private static readonly ModBenchmarkCase[] Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 3U, "UInt32:3"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt64, 3UL, "UInt64:3"),
|
||||
new(ModNumericType.UInt64, 8191UL, "UInt64:8191"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt64, ulong.MaxValue - 17UL, "UInt64:Max-17"),
|
||||
new(ModNumericType.UInt128, 3UL, "UInt128:3"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
new(ModNumericType.UInt128, ulong.MaxValue - 17UL, "UInt128:Max-17"),
|
||||
];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod3(ulong value) =>
|
||||
((uint)(value & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 16) & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 32) & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 48) & ULongExtensions.WordBitMask)) % 3UL;
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetCases() => Cases;
|
||||
|
||||
/// <summary>
|
||||
/// `% 3` baseline across all widths; previously landed at 0.03-0.09 ns on <c>uint</c>, 0.26-0.49 ns on <c>ulong</c>,
|
||||
/// and 2.88-2.97 ns on <see cref="UInt128"/> inputs throughout the merged dataset.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ModuloOperator(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => (uint)data.Value % 3U,
|
||||
ModNumericType.UInt64 => (ulong)data.Value % 3UL,
|
||||
ModNumericType.UInt128 => (ulong)(data.Value % 3UL),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helpers that fold operands by width; they matched the modulo noise floor on <c>uint</c>, trailed by
|
||||
/// ~1.5-1.7× on <c>ulong</c>, and delivered a 6.4× speedup on <see cref="UInt128"/>.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ExtensionMethod(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => UIntExtensions.Mod3((uint)data.Value),
|
||||
ModNumericType.UInt64 => Mod3((ulong)data.Value),
|
||||
ModNumericType.UInt128 => data.Value.Mod3(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod3UInt128Benchmarks
|
||||
{
|
||||
[Params(3UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 17UL)]
|
||||
public UInt128 Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Direct `% 3` on <see cref="UInt128"/>; measured 2.88-2.97 ns (2.969 ns at value 3, 2.887 ns at 8191, 2.885 ns at 131071,
|
||||
/// 2.884 ns at 2147483647, 2.933 ns near ulong.Max).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return (ulong)(Value % 3UL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper folding the high and low halves; trimmed timings to 0.455-0.475 ns (0.4549 ns at value 3, 0.4562 ns at
|
||||
/// 8191, 0.4656 ns at 131071, 0.4710 ns at 2147483647, 0.4750 ns near ulong.Max), about 6.4x faster than the baseline.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Value.Mod3();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod3UIntBenchmarks
|
||||
{
|
||||
[Params(3U, 2047U, 65535U, 2147483647U)]
|
||||
public uint Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 3` baseline; the near-zero timings (0.0320 ns at value 3, 0.0918 ns at 2047, 0.0355 ns at 65535, 0.0309 ns at
|
||||
/// 2147483647) reflect the noise floor flagged by BenchmarkDotNet.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public uint ModuloOperator()
|
||||
{
|
||||
return Value % 3U;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper that folds digits; produced 0.0217 ns at value 3, 0.0303 ns at 2047, 0.0597 ns at 65535, and 0.0350 ns at
|
||||
/// 2147483647, tracking the baseline within measurement noise.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public uint ExtensionMethod()
|
||||
{
|
||||
return UIntExtensions.Mod3(Value);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod3ULongBenchmarks
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod3(ulong value) =>
|
||||
((uint)(value & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 16) & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 32) & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 48) & ULongExtensions.WordBitMask)) % 3UL;
|
||||
|
||||
[Params(3UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 17UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 3` baseline; delivered 0.259-0.286 ns for the standard values (0.2640 ns at 3, 0.2599 ns at 8191, 0.2620 ns at 131071,
|
||||
/// 0.2858 ns at 2147483647) and 0.4916 ns near ulong.Max.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return Value % 3UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Byte-slice helper; measured 0.441 ns at value 3, 0.437 ns at 8191, 0.438 ns at 131071, 0.436 ns at 2147483647, and 0.448 ns
|
||||
/// near ulong.Max—about 1.5-1.7x slower than the `%` baseline but still sub-nanosecond.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Mod3(Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod5Benchmarks
|
||||
{
|
||||
private static readonly ModBenchmarkCase[] Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 3U, "UInt32:3"),
|
||||
new(ModNumericType.UInt32, 5U, "UInt32:5"),
|
||||
new(ModNumericType.UInt32, 6U, "UInt32:6"),
|
||||
new(ModNumericType.UInt32, 8U, "UInt32:8"),
|
||||
new(ModNumericType.UInt32, 10U, "UInt32:10"),
|
||||
new(ModNumericType.UInt32, 11U, "UInt32:11"),
|
||||
new(ModNumericType.UInt32, 32U, "UInt32:32"),
|
||||
new(ModNumericType.UInt32, 64U, "UInt32:64"),
|
||||
new(ModNumericType.UInt32, 128U, "UInt32:128"),
|
||||
new(ModNumericType.UInt32, 256U, "UInt32:256"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 8191U, "UInt32:8191"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 131071U, "UInt32:131071"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 1024U, "UInt32:Max-1024"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 511U, "UInt32:Max-511"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 255U, "UInt32:Max-255"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 127U, "UInt32:Max-127"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 63U, "UInt32:Max-63"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 31U, "UInt32:Max-31"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue - 17U, "UInt32:Max-17"),
|
||||
new(ModNumericType.UInt32, uint.MaxValue, "UInt32:Max"),
|
||||
new(ModNumericType.UInt64, 5UL, "UInt64:5"),
|
||||
new(ModNumericType.UInt64, 8191UL, "UInt64:8191"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt64, ulong.MaxValue - 31UL, "UInt64:Max-31"),
|
||||
new(ModNumericType.UInt128, 5UL, "UInt128:5"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
new(ModNumericType.UInt128, ulong.MaxValue - 31UL, "UInt128:Max-31"),
|
||||
];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod5(ulong value) =>
|
||||
((uint)(value & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 16) & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 32) & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 48) & ULongExtensions.WordBitMask)) % 5UL;
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetCases() => Cases;
|
||||
|
||||
/// <summary>
|
||||
/// `% 5` baseline; maintained 0.03-0.12 ns on <c>uint</c>, 0.26-0.27 ns on <c>ulong</c>, and 2.84-2.93 ns on
|
||||
/// <see cref="UInt128"/> values across the unified dataset.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ModuloOperator(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => (uint)data.Value % 5U,
|
||||
ModNumericType.UInt64 => (ulong)data.Value % 5UL,
|
||||
ModNumericType.UInt128 => (ulong)(data.Value % 5UL),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helpers; effectively tied with modulo on <c>uint</c>, about 1.6-1.7× slower on <c>ulong</c>, and roughly 6×
|
||||
/// faster than `%` on <see cref="UInt128"/> inputs.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ExtensionMethod(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => ((uint)data.Value).Mod5(),
|
||||
ModNumericType.UInt64 => Mod5((ulong)data.Value),
|
||||
ModNumericType.UInt128 => data.Value.Mod5(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod5UInt128Benchmarks
|
||||
{
|
||||
[Params(5UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 31UL)]
|
||||
public UInt128 Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 5` baseline; ran at 2.84-2.93 ns (2.840 ns at value 5, 2.874 ns at 8191, 2.870 ns at 131071, 2.895 ns at 2147483647,
|
||||
/// 2.927 ns near ulong.Max).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return (ulong)(Value % 5UL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper that folds high and low halves; trimmed execution to 0.47-0.49 ns (0.4706 ns at value 5, 0.4691 ns at
|
||||
/// 8191, 0.4644 ns at 131071, 0.4847 ns at 2147483647, 0.4669 ns near ulong.Max), about 6x faster.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Value.Mod5();
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod5UIntBenchmarks
|
||||
{
|
||||
[Params(
|
||||
3U,
|
||||
5U,
|
||||
6U,
|
||||
8U,
|
||||
10U,
|
||||
11U,
|
||||
32U,
|
||||
64U,
|
||||
128U,
|
||||
256U,
|
||||
2047U,
|
||||
8191U,
|
||||
65535U,
|
||||
131071U,
|
||||
2147483647U,
|
||||
uint.MaxValue - 1024U,
|
||||
uint.MaxValue - 511U,
|
||||
uint.MaxValue - 255U,
|
||||
uint.MaxValue - 127U,
|
||||
uint.MaxValue - 63U,
|
||||
uint.MaxValue - 31U,
|
||||
uint.MaxValue - 17U,
|
||||
uint.MaxValue)]
|
||||
public uint Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 5` baseline; times hovered around 0.03 ns with the expected zero-measurement noise (0.0296 ns at value 5, 0.0671 ns at 6,
|
||||
/// 0.0326 ns at 8, 0.0289 ns at 10, 0.1236 ns at 11, dropping back to ~0.03 ns for large values).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public uint ModuloOperator()
|
||||
{
|
||||
return Value % 5U;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper; typically within 0.03-0.04 ns (0.0349 ns at value 3, 0.0294 ns at 8, 0.0305 ns at 65535, 0.0270 ns at 131071,
|
||||
/// 0.0327 ns at 2147483647) though the dataset contains an outlier at value 11 (0.0315 ns) and value 64 (0.0356 ns).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public uint ExtensionMethod()
|
||||
{
|
||||
return Value.Mod5();
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod5ULongBenchmarks
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Mod5(ulong value) =>
|
||||
((uint)(value & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 16) & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 32) & ULongExtensions.WordBitMask)
|
||||
+ (uint)((value >> 48) & ULongExtensions.WordBitMask)) % 5UL;
|
||||
|
||||
[Params(5UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 31UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 5` baseline; measured 0.2657 ns at value 5, 0.2622 ns at 8191, 0.2640 ns at 131071, 0.2645 ns at 2147483647, and 0.2696 ns
|
||||
/// near ulong.Max.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return Value % 5UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper; slower at 0.431-0.448 ns (0.4305 ns at value 5, 0.4404 ns at 8191, 0.4359 ns at 131071, 0.4361 ns at
|
||||
/// 2147483647, 0.4481 ns near ulong.Max), roughly 1.6-1.7x the `%` cost.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Mod5(Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 3)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod6Benchmarks
|
||||
{
|
||||
private static readonly byte[] Mod6Lookup = [0, 3, 4, 1, 2, 5];
|
||||
|
||||
private static readonly ModBenchmarkCase[] Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 6U, "UInt32:6"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt64, 3UL, "UInt64:3"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt64, ulong.MaxValue - 1024UL, "UInt64:Max-1024"),
|
||||
new(ModNumericType.UInt128, 6UL, "UInt128:6"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
new(ModNumericType.UInt128, ulong.MaxValue - 63UL, "UInt128:Max-63"),
|
||||
];
|
||||
|
||||
private static readonly ModBenchmarkCase[] UInt64Cases =
|
||||
[
|
||||
new(ModNumericType.UInt64, 3UL, "UInt64:3"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt64, ulong.MaxValue - 1024UL, "UInt64:Max-1024"),
|
||||
];
|
||||
|
||||
private static readonly Mod6LoopCase[] LoopCases =
|
||||
[
|
||||
new(3UL, "Loop:3"),
|
||||
new(131071UL, "Loop:131071"),
|
||||
new(ulong.MaxValue - 1024UL, "Loop:Max-1024"),
|
||||
];
|
||||
|
||||
private const int Iterations = 10_000_000;
|
||||
private const ulong Divisor = 6UL;
|
||||
private static readonly ulong FastDivMul = (ulong)(((UInt128)1 << 64) / Divisor);
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetCases() => Cases;
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetUInt64Cases() => UInt64Cases;
|
||||
|
||||
public static IEnumerable<Mod6LoopCase> GetLoopCases() => LoopCases;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint Mod6(uint value) => Mod6Lookup[(int)(((value % 3U) << 1) | (value & 1U))];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod6(ulong value) => Mod6Lookup[(int)(((value % 3UL) << 1) | (value & 1UL))];
|
||||
|
||||
/// <summary>
|
||||
/// `% 6` baseline across the merged scalar dataset; previously timed at 0.03-0.05 ns on <c>uint</c>, 0.26-0.27 ns on
|
||||
/// <c>ulong</c>, and 2.87-2.91 ns on <see cref="UInt128"/> inputs.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ModuloOperator(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => (uint)data.Value % 6U,
|
||||
ModNumericType.UInt64 => (ulong)data.Value % 6UL,
|
||||
ModNumericType.UInt128 => (ulong)(data.Value % 6UL),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Division-based scalar helpers where available; matches the modulo cost on <c>ulong</c> inputs.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetUInt64Cases))]
|
||||
public ulong DivisionBased(ModBenchmarkCase data)
|
||||
{
|
||||
ulong value = (ulong)data.Value;
|
||||
ulong quotient = value / Divisor;
|
||||
return value - quotient * Divisor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FastDivHigh scalar helper for <c>ulong</c>; remains 1.0-1.6× slower than the `%` baseline across the dataset.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetUInt64Cases))]
|
||||
public ulong FastDivHigh(ModBenchmarkCase data)
|
||||
{
|
||||
ulong value = (ulong)data.Value;
|
||||
ulong quotient = value.FastDiv64(Divisor, FastDivMul);
|
||||
return value - quotient * Divisor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup helpers; significantly slower on <c>uint</c> and <c>ulong</c> but 2.4-2.5× faster on <see cref="UInt128"/>.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ExtensionMethod(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => Mod6((uint)data.Value),
|
||||
ModNumericType.UInt64 => Mod6((ulong)data.Value),
|
||||
ModNumericType.UInt128 => data.Value.Mod6(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `% 6` baseline inside the 10,000,000-iteration loop; measured 1.02-1.04 ns depending on the starting value.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = Iterations)]
|
||||
[ArgumentsSource(nameof(GetLoopCases))]
|
||||
public ulong ModuloOperatorLoop(Mod6LoopCase data)
|
||||
{
|
||||
ulong checksum = 0UL;
|
||||
ulong value = data.StartValue;
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
checksum += value % Divisor;
|
||||
value += (ulong)((i & 15) + 1);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Division-based loop helper; mirrors the modulo timings with small deviations across the dataset.
|
||||
/// </summary>
|
||||
[Benchmark(OperationsPerInvoke = Iterations)]
|
||||
[ArgumentsSource(nameof(GetLoopCases))]
|
||||
public ulong DivisionBasedLoop(Mod6LoopCase data)
|
||||
{
|
||||
ulong checksum = 0UL;
|
||||
ulong value = data.StartValue;
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
ulong quotient = value / Divisor;
|
||||
checksum += value - quotient * Divisor;
|
||||
value += (ulong)((i & 15) + 1);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FastDivHigh loop helper; remained ~1.6× slower than the modulo baseline across the looped dataset.
|
||||
/// </summary>
|
||||
[Benchmark(OperationsPerInvoke = Iterations)]
|
||||
[ArgumentsSource(nameof(GetLoopCases))]
|
||||
public ulong FastDivHighLoop(Mod6LoopCase data)
|
||||
{
|
||||
ulong checksum = 0UL;
|
||||
ulong value = data.StartValue;
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
ulong quotient = value.FastDiv64(Divisor, FastDivMul);
|
||||
checksum += value - quotient * Divisor;
|
||||
value += (ulong)((i & 15) + 1);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup-based loop helper; effectively matched the modulo baseline while removing the `%` instruction.
|
||||
/// </summary>
|
||||
[Benchmark(OperationsPerInvoke = Iterations)]
|
||||
[ArgumentsSource(nameof(GetLoopCases))]
|
||||
public ulong ExtensionMethodLoop(Mod6LoopCase data)
|
||||
{
|
||||
ulong checksum = 0UL;
|
||||
ulong value = data.StartValue;
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
checksum += Mod6(value);
|
||||
value += (ulong)((i & 15) + 1);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
public readonly struct Mod6LoopCase
|
||||
{
|
||||
public Mod6LoopCase(ulong startValue, string display)
|
||||
{
|
||||
StartValue = startValue;
|
||||
Display = display;
|
||||
}
|
||||
|
||||
public ulong StartValue { get; }
|
||||
|
||||
private string Display { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Display;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 3)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod6ComparisonBenchmarks
|
||||
{
|
||||
private static readonly byte[] Mod6Lookup = [0, 3, 4, 1, 2, 5];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Mod6(ulong value) => Mod6Lookup[(int)(((value % 3UL) << 1) | (value & 1UL))];
|
||||
|
||||
private const int Iterations = 10_000_000;
|
||||
private const ulong Divisor = 6UL;
|
||||
private static readonly ulong FastDivMul = (ulong)(((UInt128)1 << 64) / Divisor);
|
||||
|
||||
[Params(3UL, 131071UL, ulong.MaxValue - 1024UL)]
|
||||
public ulong StartValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 6` baseline inside the loop; measured 1.02 ns at StartValue 3, 1.035 ns at 131071, and 1.011 ns near ulong.Max.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true, OperationsPerInvoke = Iterations)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
ulong checksum = 0UL;
|
||||
ulong value = StartValue;
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
checksum += value % Divisor;
|
||||
value += (ulong)((i & 15) + 1);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `value / 6` with a multiply back; varied between 0.962 ns and 1.063 ns (0.9915 ns at StartValue 3, 1.0633 ns at 131071,
|
||||
/// 0.9617 ns near ulong.Max), roughly matching the baseline.
|
||||
/// </summary>
|
||||
[Benchmark(OperationsPerInvoke = Iterations)]
|
||||
public ulong DivisionBased()
|
||||
{
|
||||
ulong checksum = 0UL;
|
||||
ulong value = StartValue;
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
ulong quotient = value / Divisor;
|
||||
checksum += value - quotient * Divisor;
|
||||
value += (ulong)((i & 15) + 1);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FastDivHigh helper; landed at 1.67-1.71 ns (1.691 ns at StartValue 3, 1.706 ns at 131071, 1.673 ns near ulong.Max), making it
|
||||
/// ~1.65x slower than `%`.
|
||||
/// </summary>
|
||||
[Benchmark(OperationsPerInvoke = Iterations)]
|
||||
public ulong FastDivHigh()
|
||||
{
|
||||
ulong checksum = 0UL;
|
||||
ulong value = StartValue;
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
ulong quotient = value.FastDiv64(Divisor, FastDivMul);
|
||||
checksum += value - quotient * Divisor;
|
||||
value += (ulong)((i & 15) + 1);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup-based helper argued for adoption; ran in 1.06-1.07 ns (1.0605 ns at StartValue 3, 1.0696 ns at 131071, 1.0604 ns near
|
||||
/// ulong.Max), essentially matching the baseline with slightly higher stability.
|
||||
/// </summary>
|
||||
[Benchmark(OperationsPerInvoke = Iterations)]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
ulong checksum = 0UL;
|
||||
ulong value = StartValue;
|
||||
|
||||
for (int i = 0; i < Iterations; i++)
|
||||
{
|
||||
checksum += Mod6(value);
|
||||
value += (ulong)((i & 15) + 1);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod6UInt128Benchmarks
|
||||
{
|
||||
[Params(6UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 63UL)]
|
||||
public UInt128 Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 6` baseline; recorded 2.89-2.91 ns on the smaller values and 2.874 ns at 2147483647, 2.908 ns near ulong.Max.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return (ulong)(Value % 6UL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper (<see cref="UInt128Extensions.Mod6"/>); reduced the timings to 1.16-1.24 ns (1.180 ns at value 6, 1.164 ns at
|
||||
/// 8191, 1.158 ns at 131071, 1.241 ns at 2147483647, 1.167 ns near ulong.Max), roughly 2.4-2.5x faster.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Value.Mod6();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod6UIntBenchmarks
|
||||
{
|
||||
private static readonly byte[] Mod6Lookup = [0, 3, 4, 1, 2, 5];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Mod6(uint value) => Mod6Lookup[(int)(((value % 3U) << 1) | (value & 1U))];
|
||||
|
||||
[Params(6U, 2047U, 65535U, 2147483647U)]
|
||||
public uint Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 6` baseline; BenchmarkDotNet highlighted zero-measurement noise with means of 0.0348 ns at value 6, 0.0277 ns at 2047,
|
||||
/// 0.0492 ns at 65535, and 0.0385 ns at 2147483647.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public uint ModuloOperator()
|
||||
{
|
||||
return Value % 6U;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup-based helper; significantly slower at 0.433 ns (value 6), 0.452 ns (2047), 0.454 ns (65535), and 0.459 ns
|
||||
/// (2147483647), about 11-19x the raw modulo cost.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public uint ExtensionMethod()
|
||||
{
|
||||
return Mod6(Value);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 3)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod6ULongBenchmarks
|
||||
{
|
||||
private static readonly byte[] Mod6Lookup = [0, 3, 4, 1, 2, 5];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Mod6(ulong value) => Mod6Lookup[(int)(((value % 3UL) << 1) | (value & 1UL))];
|
||||
|
||||
[Params(3UL, 131071UL, ulong.MaxValue - 1024UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 6` baseline; recorded 0.264 ns at Value 3, 0.262 ns at 131071, and 0.271 ns near ulong.Max.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return Value % 6UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Division-based reduction (`value / 6`); timings were 0.277 ns, 0.284 ns, and 0.287 ns across the three start values.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong DivisionBased()
|
||||
{
|
||||
ulong quotient = Value / 6UL;
|
||||
return Value - quotient * 6UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FastDivHigh helper; significantly slower at 1.24 ns (Value 3), 1.04 ns (131071), and 1.17 ns (near ulong.Max).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong FastDivHigh()
|
||||
{
|
||||
ulong quotient = Value.FastDiv64(6UL, (ulong)(((UInt128)1 << 64) / 6UL));
|
||||
return Value - quotient * 6UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lookup-based helper; cost 0.461 ns at Value 3, 0.467 ns at 131071, 0.467 ns near ulong.Max—about 1.7x slower than the modulo.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Mod6(Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod7Benchmarks
|
||||
{
|
||||
private static readonly ModBenchmarkCase[] Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 7U, "UInt32:7"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt64, 7UL, "UInt64:7"),
|
||||
new(ModNumericType.UInt64, 8191UL, "UInt64:8191"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt64, ulong.MaxValue - 63UL, "UInt64:Max-63"),
|
||||
new(ModNumericType.UInt128, 7UL, "UInt128:7"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
new(ModNumericType.UInt128, ulong.MaxValue - 127UL, "UInt128:Max-127"),
|
||||
];
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetCases() => Cases;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint Mod7(uint value)
|
||||
{
|
||||
uint remainder = 0U;
|
||||
while (value != 0U)
|
||||
{
|
||||
remainder += value & 7U;
|
||||
value >>= 3;
|
||||
if (remainder >= 7U)
|
||||
{
|
||||
remainder -= 7U;
|
||||
}
|
||||
}
|
||||
|
||||
return remainder >= 7U ? remainder - 7U : remainder;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod7(ulong value)
|
||||
{
|
||||
ulong remainder = ((uint)value % 7U) + (((uint)(value >> 32) % 7U) << 2);
|
||||
while (remainder >= 7UL)
|
||||
{
|
||||
remainder -= 7UL;
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `% 7` baseline across the merged dataset; hovered near the noise floor on <c>uint</c>, 0.27 ns on <c>ulong</c>, and
|
||||
/// 2.86-2.91 ns on <see cref="UInt128"/> inputs.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ModuloOperator(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => (uint)data.Value % 7U,
|
||||
ModNumericType.UInt64 => (ulong)data.Value % 7UL,
|
||||
ModNumericType.UInt128 => (ulong)(data.Value % 7UL),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helpers; dramatically slower on <c>uint</c>, modestly slower on <c>ulong</c>, and 3.2-4.2× faster on
|
||||
/// <see cref="UInt128"/> inputs where the folding helpers shine.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ExtensionMethod(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => Mod7((uint)data.Value),
|
||||
ModNumericType.UInt64 => Mod7((ulong)data.Value),
|
||||
ModNumericType.UInt128 => data.Value.Mod7(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod7UInt128Benchmarks
|
||||
{
|
||||
[Params(7UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 127UL)]
|
||||
public UInt128 Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 7` baseline; timings clustered around 2.86-2.91 ns (2.860 ns at value 7, 2.887 ns at 8191, 2.873 ns at 131071,
|
||||
/// 2.878 ns at 2147483647, 2.915 ns near ulong.Max).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return (ulong)(Value % 7UL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper; achieved 0.676 ns (value 7), 0.675 ns (8191), 0.722 ns (131071), 0.882 ns (2147483647), and 0.676 ns near
|
||||
/// ulong.Max—between 3.2x and 4.2x faster than the baseline.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Value.Mod7();
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod7UIntBenchmarks
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint Mod7(uint value)
|
||||
{
|
||||
uint remainder = 0U;
|
||||
while (value != 0U)
|
||||
{
|
||||
remainder += value & 7U;
|
||||
value >>= 3;
|
||||
if (remainder >= 7U)
|
||||
{
|
||||
remainder -= 7U;
|
||||
}
|
||||
}
|
||||
|
||||
return remainder >= 7U ? remainder - 7U : remainder;
|
||||
}
|
||||
|
||||
[Params(7U, 2047U, 65535U, 2147483647U)]
|
||||
public uint Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 7` baseline; again near the noise floor with means of 0.0354 ns (value 7), 0.0276 ns (2047), 0.0282 ns (65535), and
|
||||
/// 0.0422 ns (2147483647).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public uint ModuloOperator()
|
||||
{
|
||||
return Value % 7U;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper; substantially slower at 0.268 ns (value 7), 1.217 ns (2047), 2.334 ns (65535), and 4.579 ns (2147483647)—up to
|
||||
/// 110x the baseline.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public uint ExtensionMethod()
|
||||
{
|
||||
return Mod7(Value);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod7ULongBenchmarks
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Mod7(ulong value)
|
||||
{
|
||||
ulong remainder = ((uint)value % 7U) + (((uint)(value >> 32) % 7U) << 2);
|
||||
while (remainder >= 7UL)
|
||||
{
|
||||
remainder -= 7UL;
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
[Params(7UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 63UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 7` baseline; 0.274 ns at value 7, 0.270 ns at 8191, 0.268 ns at 131071, 0.270 ns at 2147483647, and 0.275 ns near ulong.Max.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return Value % 7UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension helper; 0.433 ns at value 7, 0.443 ns at 8191, 0.436 ns at 131071, 0.438 ns at 2147483647, but 0.684 ns near
|
||||
/// ulong.Max—about 1.6-2.5x slower overall.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Mod7(Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod8Benchmarks
|
||||
{
|
||||
private static readonly ModBenchmarkCase[] Cases =
|
||||
[
|
||||
new(ModNumericType.UInt32, 8U, "UInt32:8"),
|
||||
new(ModNumericType.UInt32, 2047U, "UInt32:2047"),
|
||||
new(ModNumericType.UInt32, 65535U, "UInt32:65535"),
|
||||
new(ModNumericType.UInt32, 2147483647U, "UInt32:2147483647"),
|
||||
new(ModNumericType.UInt64, 8UL, "UInt64:8"),
|
||||
new(ModNumericType.UInt64, 8191UL, "UInt64:8191"),
|
||||
new(ModNumericType.UInt64, 131071UL, "UInt64:131071"),
|
||||
new(ModNumericType.UInt64, 2147483647UL, "UInt64:2147483647"),
|
||||
new(ModNumericType.UInt64, ulong.MaxValue - 127UL, "UInt64:Max-127"),
|
||||
new(ModNumericType.UInt128, 8UL, "UInt128:8"),
|
||||
new(ModNumericType.UInt128, 8191UL, "UInt128:8191"),
|
||||
new(ModNumericType.UInt128, 131071UL, "UInt128:131071"),
|
||||
new(ModNumericType.UInt128, 2147483647UL, "UInt128:2147483647"),
|
||||
new(ModNumericType.UInt128, ulong.MaxValue - 255UL, "UInt128:Max-255"),
|
||||
];
|
||||
|
||||
public static IEnumerable<ModBenchmarkCase> GetCases() => Cases;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod8(ulong value)
|
||||
{
|
||||
return value & 7UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `% 8` baseline across the merged dataset; effectively at the noise floor on <c>uint</c> and <c>ulong</c>, and 2.86-2.89 ns
|
||||
/// on <see cref="UInt128"/> inputs.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ModuloOperator(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => (uint)data.Value % 8U,
|
||||
ModNumericType.UInt64 => (ulong)data.Value % 8UL,
|
||||
ModNumericType.UInt128 => (ulong)(data.Value % 8UL),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask helpers; match `%` on 32-bit inputs, are typically faster on <c>ulong</c>, and deliver 140-280× wins on
|
||||
/// <see cref="UInt128"/> by avoiding division.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[ArgumentsSource(nameof(GetCases))]
|
||||
public ulong ExtensionMethod(ModBenchmarkCase data)
|
||||
{
|
||||
return data.Type switch
|
||||
{
|
||||
ModNumericType.UInt32 => ((uint)data.Value).Mod8(),
|
||||
ModNumericType.UInt64 => Mod8((ulong)data.Value),
|
||||
ModNumericType.UInt128 => data.Value.Mod8(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod8UInt128Benchmarks
|
||||
{
|
||||
[Params(8UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 255UL)]
|
||||
public UInt128 Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 8` baseline; 2.86-2.89 ns across the values (2.864 ns at value 8, 2.878 ns at 8191, 2.895 ns at 131071, 2.890 ns at
|
||||
/// 2147483647, 2.894 ns near ulong.Max).
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return (ulong)(Value % 8UL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask helper; reduced the cost to 0.010-0.020 ns (0.0170 ns at value 8, 0.0144 ns at 8191, 0.0134 ns at 131071, 0.0105 ns at
|
||||
/// 2147483647, 0.0203 ns near ulong.Max), delivering a 140-280x speedup.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Value.Mod8();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
[MemoryDiagnoser]
|
||||
public class Mod8UIntBenchmarks
|
||||
{
|
||||
[Params(8U, 2047U, 65535U, 2147483647U)]
|
||||
public uint Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// `% 8` baseline; means stayed around 0.011-0.015 ns (0.0109 ns at value 8, 0.0151 ns at 2047, 0.0123 ns at 65535, 0.0122 ns at
|
||||
/// 2147483647), i.e. indistinguishable from the empty-loop costs.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public uint ModuloOperator()
|
||||
{
|
||||
return Value % 8U;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask helper (`value & 7U`); returned 0.0032 ns at value 8, 0.0113 ns at 2047, 0.0191 ns at 65535, and 0.0150 ns at
|
||||
/// 2147483647—roughly matching the baseline within measurement noise.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public uint ExtensionMethod()
|
||||
{
|
||||
return Value.Mod8();
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
public class Mod8ULongBenchmarks
|
||||
{
|
||||
[Params(8UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 127UL)]
|
||||
public ulong Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Uses the built-in <c>% 8</c>; measured 0.0224 ns on Value=8, ~0 ns within the measurement noise floor for 8,191,
|
||||
/// 0.0069 ns for 131,071, 0.0117 ns for 2,147,483,647, and 0.107 ns for the near-maximum value, making it the fastest
|
||||
/// option except when the branch-free mask wins on huge operands.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ModuloOperator()
|
||||
{
|
||||
return Value % 8UL;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the branch-free mask helper; costs 0.0344 ns on Value=8, 0.0296 ns on 8,191, 0.0114 ns on 131,071,
|
||||
/// 0.0106 ns on 2,147,483,647, and just 0.0109 ns on the near-maximum sample, overtaking <c>%</c> once the divisor
|
||||
/// approaches 2<sup>64</sup>.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong ExtensionMethod()
|
||||
{
|
||||
return Mod8(Value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong Mod8(ulong value)
|
||||
{
|
||||
return value & 7UL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
public enum ModNumericType
|
||||
{
|
||||
UInt32,
|
||||
UInt64,
|
||||
UInt128,
|
||||
}
|
||||
|
||||
public readonly struct ModBenchmarkCase
|
||||
{
|
||||
public ModBenchmarkCase(ModNumericType type, UInt128 value, string display)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
Display = display;
|
||||
}
|
||||
|
||||
public ModNumericType Type { get; }
|
||||
|
||||
public UInt128 Value { get; }
|
||||
|
||||
private string Display { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Display;
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,10 @@ public class MontgomeryMultiplyBenchmarks
|
||||
{
|
||||
private const int MaxBatchSize = 1024;
|
||||
|
||||
private readonly ulong[] _operandsA = new ulong[MaxBatchSize];
|
||||
private readonly ulong[] _operandsB = new ulong[MaxBatchSize];
|
||||
private readonly ulong[] _moduli = new ulong[MaxBatchSize];
|
||||
private readonly ulong[] _nPrimes = new ulong[MaxBatchSize];
|
||||
|
||||
private readonly Random _random = new(1979);
|
||||
private ulong[] _operandsA = Array.Empty<ulong>();
|
||||
private ulong[] _operandsB = Array.Empty<ulong>();
|
||||
private ulong[] _moduli = Array.Empty<ulong>();
|
||||
private ulong[] _nPrimes = Array.Empty<ulong>();
|
||||
|
||||
[Params(64, 256, 1024)]
|
||||
public int BatchSize { get; set; }
|
||||
@@ -22,13 +20,64 @@ public class MontgomeryMultiplyBenchmarks
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
for (int i = 0; i < MaxBatchSize; i++)
|
||||
MontgomerySampleData data = MontgomerySampleDataCache.Instance;
|
||||
_operandsA = data.OperandsA;
|
||||
_operandsB = data.OperandsB;
|
||||
_moduli = data.Moduli;
|
||||
_nPrimes = data.NPrimes;
|
||||
}
|
||||
|
||||
private sealed class MontgomerySampleData
|
||||
{
|
||||
public MontgomerySampleData(ulong[] operandsA, ulong[] operandsB, ulong[] moduli, ulong[] nPrimes)
|
||||
{
|
||||
ulong modulus = NextOddModulus();
|
||||
_moduli[i] = modulus;
|
||||
_nPrimes[i] = ComputeMontgomeryNPrime(modulus);
|
||||
_operandsA[i] = (ulong)_random.NextInt64(0, (long)modulus);
|
||||
_operandsB[i] = (ulong)_random.NextInt64(0, (long)modulus);
|
||||
OperandsA = operandsA;
|
||||
OperandsB = operandsB;
|
||||
Moduli = moduli;
|
||||
NPrimes = nPrimes;
|
||||
}
|
||||
|
||||
public ulong[] OperandsA { get; }
|
||||
public ulong[] OperandsB { get; }
|
||||
public ulong[] Moduli { get; }
|
||||
public ulong[] NPrimes { get; }
|
||||
}
|
||||
|
||||
private static class MontgomerySampleDataCache
|
||||
{
|
||||
private static readonly Lazy<MontgomerySampleData> Cache = new(Create);
|
||||
|
||||
public static MontgomerySampleData Instance => Cache.Value;
|
||||
|
||||
private static MontgomerySampleData Create()
|
||||
{
|
||||
Random random = new(1979);
|
||||
var operandsA = new ulong[MaxBatchSize];
|
||||
var operandsB = new ulong[MaxBatchSize];
|
||||
var moduli = new ulong[MaxBatchSize];
|
||||
var nPrimes = new ulong[MaxBatchSize];
|
||||
|
||||
for (int i = 0; i < MaxBatchSize; i++)
|
||||
{
|
||||
ulong modulus = NextOddModulus(random);
|
||||
moduli[i] = modulus;
|
||||
nPrimes[i] = ComputeMontgomeryNPrime(modulus);
|
||||
operandsA[i] = (ulong)random.NextInt64(0, (long)modulus);
|
||||
operandsB[i] = (ulong)random.NextInt64(0, (long)modulus);
|
||||
}
|
||||
|
||||
return new MontgomerySampleData(operandsA, operandsB, moduli, nPrimes);
|
||||
}
|
||||
|
||||
private static ulong NextOddModulus(Random random)
|
||||
{
|
||||
ulong modulus = (ulong)random.NextInt64(3, long.MaxValue);
|
||||
if ((modulus & 1UL) == 0UL)
|
||||
{
|
||||
modulus++;
|
||||
}
|
||||
|
||||
return modulus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,17 +117,6 @@ public class MontgomeryMultiplyBenchmarks
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private ulong NextOddModulus()
|
||||
{
|
||||
ulong modulus = (ulong)_random.NextInt64(3, long.MaxValue);
|
||||
if ((modulus & 1UL) == 0UL)
|
||||
{
|
||||
modulus++;
|
||||
}
|
||||
|
||||
return modulus;
|
||||
}
|
||||
|
||||
private static ulong OriginalMontgomeryMultiply(ulong a, ulong b, ulong modulus, ulong nPrime)
|
||||
{
|
||||
ulong tLow = unchecked(a * b);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,515 +0,0 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
public class Pow2MontgomeryModBenchmarks
|
||||
{
|
||||
private const int SampleCount = 256;
|
||||
|
||||
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly ulong[] _smallExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _largeExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _smallCycles = new ulong[SampleCount];
|
||||
private readonly ulong[] _largeCycles = new ulong[SampleCount];
|
||||
|
||||
private readonly Random _random = new(13);
|
||||
|
||||
public enum InputScale
|
||||
{
|
||||
Small,
|
||||
Large
|
||||
}
|
||||
|
||||
[ParamsAllValues]
|
||||
public InputScale Scale { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong smallModulus = NextSmallOddModulus();
|
||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
_smallExponents[i] = NextSmallExponent();
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
|
||||
_largeExponents[i] = NextLargeExponent();
|
||||
_largeCycles[i] = largeCycle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong BaselineCpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
||||
///
|
||||
/// This benchmark failed running, hanging up early before reaching workload testing.
|
||||
/// </summary>
|
||||
// [Benchmark]
|
||||
public ulong BaselineGpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BaselineWithKnownCycleCpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out var cycles);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BaselineWithKnownCycleGpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out var cycles);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong LeftToRight()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong Batched4()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong SlidingWindow()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BinaryMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong PrecomputedTableMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Relies on <see cref="BigInteger.ModPow"/>; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong BigIntegerMod()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
||||
{
|
||||
if (Scale == InputScale.Small)
|
||||
{
|
||||
exponents = _smallExponents;
|
||||
divisors = _smallDivisors;
|
||||
cycles = _smallCycles;
|
||||
}
|
||||
else
|
||||
{
|
||||
exponents = _largeExponents;
|
||||
divisors = _largeDivisors;
|
||||
cycles = _largeCycles;
|
||||
}
|
||||
}
|
||||
|
||||
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
|
||||
|
||||
private ulong NextLargeExponent() => (ulong)_random.NextInt64(1L << 60, long.MaxValue);
|
||||
|
||||
private ulong NextSmallOddModulus()
|
||||
{
|
||||
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
|
||||
return value | 1UL;
|
||||
}
|
||||
|
||||
private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle()
|
||||
{
|
||||
int bitLength = _random.Next(48, 64);
|
||||
ulong modulus = (1UL << bitLength) - 1UL;
|
||||
return (modulus, (ulong)bitLength);
|
||||
}
|
||||
|
||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
|
||||
}
|
||||
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
nPrime,
|
||||
montgomeryOne,
|
||||
montgomeryTwo,
|
||||
montgomeryTwoSquared);
|
||||
}
|
||||
|
||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % 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);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
|
||||
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
|
||||
{
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
|
||||
if (((exponent >> bitIndex) & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||
}
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong remainingExponent = exponent;
|
||||
|
||||
while (remainingExponent != 0UL)
|
||||
{
|
||||
ulong nibble = remainingExponent & 0xFUL;
|
||||
|
||||
ulong base0 = baseVal;
|
||||
ulong base1 = base0.MontgomeryMultiply(base0, modulus, nPrime);
|
||||
ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime);
|
||||
ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime);
|
||||
|
||||
if ((nibble & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(base0, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 2UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(base1, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 4UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(base2, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 8UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(base3, modulus, nPrime);
|
||||
}
|
||||
|
||||
baseVal = base3.MontgomeryMultiply(base3, modulus, nPrime);
|
||||
remainingExponent >>= 4;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong baseVal = divisor.MontgomeryTwo;
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
const int WindowSize = 5;
|
||||
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
|
||||
oddPowers[0] = baseVal;
|
||||
ulong baseSquared = baseVal.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||
for (int i = 1; i < oddPowers.Length; i++)
|
||||
{
|
||||
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiply(baseSquared, modulus, nPrime);
|
||||
}
|
||||
|
||||
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
|
||||
while (bitIndex >= 0)
|
||||
{
|
||||
if (((exponent >> bitIndex) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
bitIndex--;
|
||||
continue;
|
||||
}
|
||||
|
||||
int windowStart = Math.Max(bitIndex - WindowSize + 1, 0);
|
||||
ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL);
|
||||
|
||||
while ((windowValue & 1UL) == 0UL)
|
||||
{
|
||||
windowValue >>= 1;
|
||||
windowStart++;
|
||||
}
|
||||
|
||||
int squares = bitIndex - windowStart + 1;
|
||||
for (int i = 0; i < squares; i++)
|
||||
{
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
}
|
||||
|
||||
result = result.MontgomeryMultiply(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
||||
bitIndex = windowStart - 1;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
ulong result = 1UL % modulus;
|
||||
ulong baseVal = 2UL % modulus;
|
||||
ulong remainingExponent = exponent;
|
||||
|
||||
while (remainingExponent > 0UL)
|
||||
{
|
||||
if ((remainingExponent & 1UL) != 0UL)
|
||||
{
|
||||
result = MultiplyMod(result, baseVal, modulus);
|
||||
}
|
||||
|
||||
remainingExponent >>= 1;
|
||||
if (remainingExponent == 0UL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
baseVal = MultiplyMod(baseVal, baseVal, modulus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return 1UL % modulus;
|
||||
}
|
||||
|
||||
Span<ulong> powers = stackalloc ulong[64];
|
||||
powers[0] = 2UL % modulus;
|
||||
int highestBit = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
|
||||
for (int index = 1; index <= highestBit; index++)
|
||||
{
|
||||
powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus);
|
||||
}
|
||||
|
||||
ulong result = 1UL % modulus;
|
||||
ulong remainingExponent = exponent;
|
||||
int bitIndex = 0;
|
||||
|
||||
while (remainingExponent > 0UL)
|
||||
{
|
||||
if ((remainingExponent & 1UL) != 0UL)
|
||||
{
|
||||
result = MultiplyMod(result, powers[bitIndex], modulus);
|
||||
}
|
||||
|
||||
remainingExponent >>= 1;
|
||||
bitIndex++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
BigInteger mod = modulus;
|
||||
BigInteger result = BigInteger.ModPow(2, exponent, mod);
|
||||
return (ulong)result;
|
||||
}
|
||||
|
||||
private static ulong MultiplyMod(ulong x, ulong y, ulong modulus)
|
||||
{
|
||||
UInt128 product = (UInt128)x * y;
|
||||
return (ulong)(product % modulus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
using System.Buffers.Binary;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
/// <remarks>
|
||||
/// Benchmark results collected on AMD Ryzen 5 5625U (.NET 8.0, launchCount: 1, warmupCount: 1, iterationCount: 5):
|
||||
/// Small sample (~1e6 random odd moduli)
|
||||
/// - MontgomeryWithoutCycle: 35.41 µs (baseline)
|
||||
/// - MontgomeryWithPrecomputedCycle: 25.81 µs (0.73× baseline)
|
||||
/// - MontgomeryWithGpuCycleComputation: 54.09 ms (1,527× baseline, includes GPU cycle calculation)
|
||||
/// Large sample (Mersenne-like moduli from 2^48-1 to 2^63-1)
|
||||
/// - MontgomeryWithoutCycle: 117.33 µs (baseline)
|
||||
/// - MontgomeryWithPrecomputedCycle: 9.12 µs (0.08× baseline)
|
||||
/// - MontgomeryWithGpuCycleComputation: 12.91 µs (0.11× baseline)
|
||||
/// </remarks>
|
||||
[MemoryDiagnoser]
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
public class Pow2MontgomeryModCycleComputationBenchmarks
|
||||
{
|
||||
private const int SampleCount = 256;
|
||||
private const ulong OneShiftedLeft63 = 1UL << 63;
|
||||
private const long OneShiftedLeft60 = 1L << 60;
|
||||
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly MontgomeryDivisorData[] _veryLargeDivisors = new MontgomeryDivisorData[SampleCount];
|
||||
private readonly UInt128[] _wideModuli = new UInt128[SampleCount];
|
||||
private readonly ulong[] _smallExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _largeExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _veryLargeExponents = new ulong[SampleCount];
|
||||
private readonly ulong[] _smallCycles = new ulong[SampleCount];
|
||||
private readonly ulong[] _largeCycles = new ulong[SampleCount];
|
||||
private readonly ulong[] _veryLargeCycles = new ulong[SampleCount];
|
||||
private readonly UInt128[] _wideCycles = new UInt128[SampleCount];
|
||||
|
||||
private readonly Random _random = new(113);
|
||||
private ulong? _previousPrimeOrder;
|
||||
private UInt128? _previousWidePrimeOrder;
|
||||
|
||||
public enum InputScale
|
||||
{
|
||||
Small,
|
||||
Large,
|
||||
VeryLarge
|
||||
}
|
||||
|
||||
[ParamsAllValues]
|
||||
public InputScale Scale { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine($"Generating sample {i}");
|
||||
#endif
|
||||
ulong smallModulus = NextSmallOddModulus();
|
||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
_smallExponents[i] = NextSmallExponent();
|
||||
_smallCycles[i] = CalculateCycleLengthWithHeuristics(smallModulus);
|
||||
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
|
||||
_largeExponents[i] = NextLargeExponent();
|
||||
_largeCycles[i] = CalculateCycleLengthWithHeuristics(largeModulus, largeCycle);
|
||||
|
||||
ulong veryLargeModulus = NextVeryLargeOddModulus();
|
||||
_veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus);
|
||||
_veryLargeExponents[i] = NextVeryLargeExponent();
|
||||
#if DEBUG
|
||||
Console.WriteLine($"Calculating cycle length {veryLargeModulus}");
|
||||
#endif
|
||||
_veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus);
|
||||
|
||||
Console.WriteLine($"Generating wide sample {i}");
|
||||
UInt128 wideModulus = NextWideOddModulus();
|
||||
_wideModuli[i] = wideModulus;
|
||||
_wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
|
||||
/// large set.
|
||||
/// </summary>
|
||||
// [Benchmark(Baseline = true)]
|
||||
public ulong MontgomeryWithoutCycleCpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
|
||||
/// large set.
|
||||
/// </summary>
|
||||
// [Benchmark(Baseline = true)]
|
||||
public ulong MontgomeryWithoutCycleGpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
|
||||
/// on the large one (0.08×).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong MontgomeryWithPrecomputedCycleCpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
|
||||
/// on the large one (0.08×).
|
||||
/// </summary>
|
||||
// [Benchmark]
|
||||
public ulong MontgomeryWithPrecomputedCycleGpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to
|
||||
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public ulong MontgomeryWithGpuCycleComputationCpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to
|
||||
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
||||
/// </summary>
|
||||
// [Benchmark]
|
||||
public ulong MontgomeryWithGpuCycleComputationGpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public ulong MontgomeryWithHeuristicCycleComputationCpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
// [Benchmark]
|
||||
public ulong MontgomeryWithHeuristicCycleComputationGpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public UInt128 HeuristicWideCycleComputation()
|
||||
{
|
||||
UInt128 checksum = UInt128.Zero;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
UInt128 cycle = CalculateCycleLengthWithHeuristics(_wideModuli[i]);
|
||||
_wideCycles[i] = cycle;
|
||||
checksum ^= cycle;
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles)
|
||||
{
|
||||
switch (Scale)
|
||||
{
|
||||
case InputScale.Small:
|
||||
exponents = _smallExponents;
|
||||
divisors = _smallDivisors;
|
||||
cycles = _smallCycles;
|
||||
break;
|
||||
case InputScale.Large:
|
||||
exponents = _largeExponents;
|
||||
divisors = _largeDivisors;
|
||||
cycles = _largeCycles;
|
||||
break;
|
||||
case InputScale.VeryLarge:
|
||||
exponents = _veryLargeExponents;
|
||||
divisors = _veryLargeDivisors;
|
||||
cycles = _veryLargeCycles;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(Scale), Scale, null);
|
||||
}
|
||||
}
|
||||
|
||||
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
|
||||
|
||||
private ulong NextLargeExponent() => (ulong)_random.NextInt64(OneShiftedLeft60, long.MaxValue);
|
||||
|
||||
private ulong NextVeryLargeExponent()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
_random.NextBytes(buffer);
|
||||
ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63);
|
||||
return value;
|
||||
}
|
||||
|
||||
private ulong NextSmallOddModulus()
|
||||
{
|
||||
ulong value = (ulong)_random.NextInt64(3L, 1_000_000L);
|
||||
return value | 1UL;
|
||||
}
|
||||
|
||||
private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle()
|
||||
{
|
||||
int bitLength = _random.Next(48, 64);
|
||||
ulong modulus = (1UL << bitLength) - 1UL;
|
||||
return (modulus, (ulong)bitLength);
|
||||
}
|
||||
|
||||
private ulong NextVeryLargeOddModulus()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
_random.NextBytes(buffer);
|
||||
ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL;
|
||||
return value;
|
||||
}
|
||||
|
||||
private UInt128 NextWideOddModulus()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[16];
|
||||
_random.NextBytes(buffer);
|
||||
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
||||
Span<byte> highSlice = buffer.Slice(8, 8);
|
||||
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(highSlice) | (1UL << 63);
|
||||
UInt128 value = ((UInt128)high << 64) | low;
|
||||
return value | UInt128.One;
|
||||
}
|
||||
|
||||
private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL);
|
||||
}
|
||||
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
nPrime,
|
||||
montgomeryOne,
|
||||
montgomeryTwo,
|
||||
montgomeryTwoSquared);
|
||||
}
|
||||
|
||||
|
||||
private ulong CalculateCycleLengthWithHeuristics(ulong modulus, ulong fallbackOrder = 0UL)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
|
||||
}
|
||||
|
||||
// When the below is uncommented, the benchmark never completes due to the primality test cost on very large random set.
|
||||
|
||||
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(modulus);
|
||||
// // bool isPrime = PrimeTester.IsPrimeInternal(modulus, CancellationToken.None);
|
||||
// if (!isPrime)
|
||||
// {
|
||||
// return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
|
||||
// }
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine("Trying heuristic. Prime order calculation");
|
||||
#endif
|
||||
ulong order = PrimeOrderCalculator.Calculate(
|
||||
modulus,
|
||||
_previousPrimeOrder,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||
|
||||
if (order != 0UL)
|
||||
{
|
||||
_previousPrimeOrder = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
_previousPrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
|
||||
}
|
||||
|
||||
private UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus)
|
||||
{
|
||||
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
|
||||
{
|
||||
return MersenneDivisorCycles.GetCycle(modulus);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
||||
#endif
|
||||
UInt128 order = PrimeOrderCalculator.Calculate(
|
||||
modulus,
|
||||
_previousWidePrimeOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
if (order != UInt128.Zero)
|
||||
{
|
||||
_previousWidePrimeOrder = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
_previousWidePrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return MersenneDivisorCycles.GetCycle(modulus);
|
||||
}
|
||||
|
||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
using System.Buffers.Binary;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
|
||||
public class Pow2MontgomeryModUInt128Benchmarks
|
||||
{
|
||||
private const int SampleCount = 256;
|
||||
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
||||
|
||||
private readonly UInt128[] _moduli = new UInt128[SampleCount];
|
||||
private readonly UInt128[] _exponents = new UInt128[SampleCount];
|
||||
|
||||
private readonly Random _random = new(19);
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
UInt128 modulus = NextWideOddModulus();
|
||||
_moduli[i] = modulus;
|
||||
|
||||
UInt128 exponent = NextWideExponent();
|
||||
_exponents[i] = exponent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current UInt128-native windowed Montgomery ladder that avoids GpuUInt128 on CPU paths.
|
||||
/// </summary>
|
||||
[Benchmark(Baseline = true)]
|
||||
public UInt128 UInt128Windowed()
|
||||
{
|
||||
UInt128 checksum = UInt128.Zero;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= _exponents[i].Pow2MontgomeryModWindowed(_moduli[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Previous CPU shim that reused the GPU-oriented GpuUInt128 implementation.
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
public UInt128 GpuStructShim()
|
||||
{
|
||||
UInt128 checksum = UInt128.Zero;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= Pow2MontgomeryModWindowedGpuShim(_exponents[i], _moduli[i]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private UInt128 NextWideOddModulus()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[16];
|
||||
_random.NextBytes(buffer);
|
||||
|
||||
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
||||
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]);
|
||||
if (high == 0UL)
|
||||
{
|
||||
high = 1UL;
|
||||
}
|
||||
|
||||
UInt128 modulus = ((UInt128)high << 64) | low;
|
||||
modulus |= UInt128.One;
|
||||
|
||||
if (modulus <= UInt128.One)
|
||||
{
|
||||
modulus += 2UL;
|
||||
}
|
||||
|
||||
return modulus;
|
||||
}
|
||||
|
||||
private UInt128 NextWideExponent()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[16];
|
||||
_random.NextBytes(buffer);
|
||||
|
||||
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
||||
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]);
|
||||
if (high == 0UL)
|
||||
{
|
||||
high = 1UL;
|
||||
}
|
||||
|
||||
return ((UInt128)high << 64) | low;
|
||||
}
|
||||
|
||||
private static UInt128 Pow2MontgomeryModWindowedGpuShim(UInt128 exponent, UInt128 modulus)
|
||||
{
|
||||
if (modulus == UInt128.One)
|
||||
{
|
||||
return UInt128.Zero;
|
||||
}
|
||||
|
||||
if (exponent == UInt128.Zero)
|
||||
{
|
||||
return UInt128.One % modulus;
|
||||
}
|
||||
|
||||
GpuUInt128 modulusGpu = new(modulus);
|
||||
GpuUInt128 exponentGpu = new(exponent);
|
||||
GpuUInt128 baseValue = new(2UL);
|
||||
if (baseValue.CompareTo(modulusGpu) >= 0)
|
||||
{
|
||||
baseValue.Sub(modulusGpu);
|
||||
}
|
||||
|
||||
if (ShouldUseSingleBit(exponentGpu))
|
||||
{
|
||||
GpuUInt128 singleBitResult = Pow2MontgomeryModSingleBit(exponentGpu, modulusGpu, baseValue);
|
||||
return (UInt128)singleBitResult;
|
||||
}
|
||||
|
||||
int bitLength = exponentGpu.GetBitLength();
|
||||
int windowSize = GetWindowSize(bitLength);
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
|
||||
Span<GpuUInt128> oddPowers = stackalloc GpuUInt128[oddPowerCount];
|
||||
InitializeOddPowers(baseValue, modulusGpu, oddPowers);
|
||||
|
||||
GpuUInt128 result = GpuUInt128.One;
|
||||
int index = bitLength - 1;
|
||||
|
||||
while (index >= 0)
|
||||
{
|
||||
if (!IsBitSet(exponentGpu, index))
|
||||
{
|
||||
result.MulMod(result, modulusGpu);
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
int windowStart = index - windowSize + 1;
|
||||
if (windowStart < 0)
|
||||
{
|
||||
windowStart = 0;
|
||||
}
|
||||
|
||||
while (!IsBitSet(exponentGpu, windowStart))
|
||||
{
|
||||
windowStart++;
|
||||
}
|
||||
|
||||
int windowBitCount = index - windowStart + 1;
|
||||
for (int square = 0; square < windowBitCount; square++)
|
||||
{
|
||||
result.MulMod(result, modulusGpu);
|
||||
}
|
||||
|
||||
ulong windowValue = ExtractWindowValue(exponentGpu, windowStart, windowBitCount);
|
||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||
GpuUInt128 factor = oddPowers[tableIndex];
|
||||
result.MulMod(factor, modulusGpu);
|
||||
|
||||
index = windowStart - 1;
|
||||
}
|
||||
|
||||
return (UInt128)result;
|
||||
}
|
||||
|
||||
private static bool ShouldUseSingleBit(GpuUInt128 exponent)
|
||||
{
|
||||
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int GetWindowSize(int bitLength)
|
||||
{
|
||||
if (bitLength <= 8)
|
||||
{
|
||||
return Math.Max(bitLength, 1);
|
||||
}
|
||||
|
||||
if (bitLength <= 23)
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (bitLength <= 79)
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (bitLength <= 239)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
|
||||
if (bitLength <= 671)
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
private static void InitializeOddPowers(GpuUInt128 baseValue, GpuUInt128 modulus, Span<GpuUInt128> oddPowers)
|
||||
{
|
||||
oddPowers[0] = baseValue;
|
||||
if (oddPowers.Length == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reusing baseValue to hold base^2 for the shared odd-power ladder.
|
||||
baseValue.MulMod(baseValue, modulus);
|
||||
for (int i = 1; i < oddPowers.Length; i++)
|
||||
{
|
||||
oddPowers[i] = oddPowers[i - 1];
|
||||
oddPowers[i].MulMod(baseValue, modulus);
|
||||
}
|
||||
}
|
||||
|
||||
private static GpuUInt128 Pow2MontgomeryModSingleBit(GpuUInt128 exponent, GpuUInt128 modulus, GpuUInt128 baseValue)
|
||||
{
|
||||
GpuUInt128 result = GpuUInt128.One;
|
||||
|
||||
while (!exponent.IsZero)
|
||||
{
|
||||
if ((exponent.Low & 1UL) != 0UL)
|
||||
{
|
||||
result.MulMod(baseValue, modulus);
|
||||
}
|
||||
|
||||
exponent.ShiftRight(1);
|
||||
if (exponent.IsZero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Reusing baseValue to store the squared base for the next iteration.
|
||||
baseValue.MulMod(baseValue, modulus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||
{
|
||||
if (bitIndex >= 64)
|
||||
{
|
||||
return ((value.High >> (bitIndex - 64)) & 1UL) != 0UL;
|
||||
}
|
||||
|
||||
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
||||
}
|
||||
|
||||
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
{
|
||||
if (windowStart != 0)
|
||||
{
|
||||
GpuUInt128 shifted = exponent;
|
||||
shifted.ShiftRight(windowStart);
|
||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
||||
return shifted.Low & mask;
|
||||
}
|
||||
|
||||
ulong directMask = (1UL << windowBitCount) - 1UL;
|
||||
return exponent.Low & directMask;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user