From 3412834f7daf77138750553c6ad12c5774f89865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Sat, 18 Oct 2025 00:29:06 +0200 Subject: [PATCH] Disable GPU heuristic cycle benchmarks --- .../GpuUInt128MulModBenchmarks.cs | 160 ++++++- .../GpuUInt128MulModByLimbBenchmarks.cs | 158 ------- .../Mod10Benchmarks.cs | 137 ++++++ .../Mod10UInt128Benchmarks.cs | 66 --- .../Mod10UIntBenchmarks.cs | 38 -- .../Mod10ULongBenchmarks.cs | 31 -- .../Mod10_8_5_3Benchmarks.cs | 131 +++++- .../Mod10_8_5_3PropertiesBenchmarks.cs | 81 ---- .../Mod10_8_5_3StepsBenchmarks.cs | 37 -- .../Mod11Benchmarks.cs | 93 ++++ .../Mod11UInt128Benchmarks.cs | 33 -- .../Mod11UIntBenchmarks.cs | 47 -- .../Mod11ULongBenchmarks.cs | 45 -- .../Mod128Benchmarks.cs | 106 +++++ .../Mod128UInt128Benchmarks.cs | 33 -- .../Mod128UIntBenchmarks.cs | 59 --- .../Mod128ULongBenchmarks.cs | 56 --- .../Mod3Benchmarks.cs | 74 ++++ .../Mod3UInt128Benchmarks.cs | 33 -- .../Mod3UIntBenchmarks.cs | 33 -- .../Mod3ULongBenchmarks.cs | 41 -- .../Mod5Benchmarks.cs | 93 ++++ .../Mod5UInt128Benchmarks.cs | 33 -- .../Mod5UIntBenchmarks.cs | 56 --- .../Mod5ULongBenchmarks.cs | 41 -- .../Mod6Benchmarks.cs | 216 +++++++++ .../Mod6ComparisonBenchmarks.cs | 100 ----- .../Mod6UInt128Benchmarks.cs | 32 -- .../Mod6UIntBenchmarks.cs | 38 -- .../Mod6ULongBenchmarks.cs | 57 --- .../Mod7Benchmarks.cs | 96 ++++ .../Mod7UInt128Benchmarks.cs | 33 -- .../Mod7UIntBenchmarks.cs | 50 --- .../Mod7ULongBenchmarks.cs | 44 -- .../Mod8Benchmarks.cs | 73 ++++ .../Mod8UInt128Benchmarks.cs | 33 -- .../Mod8UIntBenchmarks.cs | 33 -- .../Mod8ULongBenchmarks.cs | 41 -- .../ModBenchmarkCase.cs | 31 ++ .../Pow2MontgomeryModBenchmarks.cs | 243 ++++++++++- ...MontgomeryModCycleComputationBenchmarks.cs | 410 ------------------ EvenPerfectBitScanner.Benchmarks/Program.cs | 4 +- 42 files changed, 1416 insertions(+), 1833 deletions(-) delete mode 100644 EvenPerfectBitScanner.Benchmarks/GpuUInt128MulModByLimbBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/Mod10Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod10UInt128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod10UIntBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod10ULongBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3PropertiesBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3StepsBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/Mod11Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod11UInt128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod11UIntBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod11ULongBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/Mod128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod128UInt128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod128UIntBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod128ULongBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/Mod3Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod3UInt128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod3UIntBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod3ULongBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/Mod5Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod5UInt128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod5UIntBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod5ULongBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/Mod6Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod6ComparisonBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod6UInt128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod6UIntBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod6ULongBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/Mod7Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod7UInt128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod7UIntBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod7ULongBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/Mod8Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod8UInt128Benchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod8UIntBenchmarks.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Mod8ULongBenchmarks.cs create mode 100644 EvenPerfectBitScanner.Benchmarks/ModBenchmarkCase.cs delete mode 100644 EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModCycleComputationBenchmarks.cs diff --git a/EvenPerfectBitScanner.Benchmarks/GpuUInt128MulModBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/GpuUInt128MulModBenchmarks.cs index a18bd4e..0534e34 100644 --- a/EvenPerfectBitScanner.Benchmarks/GpuUInt128MulModBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/GpuUInt128MulModBenchmarks.cs @@ -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 GetInputs() => Inputs; + public static IEnumerable GetGeneralCases() => GeneralCases; + + public static IEnumerable GetByLimbCases() => ByLimbCases; /// /// 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. /// [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. /// [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); + } + + /// + /// 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. + /// + /// + /// Observed means: HighWordDominant 12.69 ns (1.00×), MixedMagnitude 16.26 ns, TinyOperands 10.74 ns. + /// + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(GetByLimbCases))] + public GpuUInt128 LegacyAllocating(GpuMulModCase input) + { + return MulModByLimbLegacy(input.Left, input.Right, input.Modulus); + } + + /// + /// 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. + /// + /// + /// Observed means: HighWordDominant 17.88 ns (1.41×), MixedMagnitude 25.14 ns, TinyOperands 12.92 ns. + /// + [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() { diff --git a/EvenPerfectBitScanner.Benchmarks/GpuUInt128MulModByLimbBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/GpuUInt128MulModByLimbBenchmarks.cs deleted file mode 100644 index f688f38..0000000 --- a/EvenPerfectBitScanner.Benchmarks/GpuUInt128MulModByLimbBenchmarks.cs +++ /dev/null @@ -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 GetInputs() => Inputs; - - /// - /// 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. - /// - /// - /// Observed means: HighWordDominant 12.69 ns (1.00×), MixedMagnitude 16.26 ns, TinyOperands 10.74 ns. - /// - [Benchmark(Baseline = true)] - public GpuUInt128 LegacyAllocating() - { - return MulModByLimbLegacy(Input.Left, Input.Right, Input.Modulus); - } - - /// - /// 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. - /// - /// - /// Observed means: HighWordDominant 17.88 ns (1.41×), MixedMagnitude 25.14 ns, TinyOperands 12.92 ns. - /// - [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; - } -} - diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10Benchmarks.cs new file mode 100644 index 0000000..b55a55c --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/Mod10Benchmarks.cs @@ -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 GetCases() => Cases; + + public static IEnumerable GetNonUInt128Cases() => NonUInt128Cases; + + public static IEnumerable 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; + } + + /// + /// `% 10` baseline; ranged from 0.04-0.32 ns on uint, ~0.26 ns on ulong, and 3.25-3.38 ns on + /// inputs across the consolidated dataset. + /// + [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)), + }; + } + + /// + /// Mask-based helpers for uint and ulong; they trail the raw `%` on 64-bit inputs and only match it on the + /// smaller width. + /// + [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)), + }; + } + + /// + /// High/low folding helper for ; measured 1.78-1.82 ns in the original suite, about 1.9× faster than `%`. + /// + [Benchmark] + [ArgumentsSource(nameof(GetUInt128Cases))] + public ulong ModWithHighLowModulo(ModBenchmarkCase data) + { + return ModWithHighLowModuloHelper(data.Value); + } + + /// + /// Loop-based folding helper for ; lands between the high/low helper and the raw modulo at 2.20-2.27 ns. + /// + [Benchmark] + [ArgumentsSource(nameof(GetUInt128Cases))] + public ulong ModWithLoopModulo(ModBenchmarkCase data) + { + return ModWithLoopModuloHelper(data.Value); + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10UInt128Benchmarks.cs deleted file mode 100644 index 8ea5208..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod10UInt128Benchmarks.cs +++ /dev/null @@ -1,66 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace EvenPerfectBitScanner.Benchmarks; - -[MemoryDiagnoser] -public class Mod10UInt128Benchmarks -{ - [Params(3UL, 8191UL, 131071UL, 2147483647UL)] - public UInt128 Value { get; set; } - - /// - /// Direct `% 10` using the 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. - /// - [Benchmark(Baseline = true)] - public UInt128 StandardModulo() - { - return Value % 10UL; - } - - /// - /// 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. - /// - [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; - } - - /// - /// 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. - /// - [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; - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10UIntBenchmarks.cs deleted file mode 100644 index 73cf903..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod10UIntBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// Direct `% 10` on ; 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. - /// - [Benchmark(Baseline = true)] - public uint ModuloOperator() - { - return Value % 10U; - } - - /// - /// 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). - /// - [Benchmark] - public uint ExtensionMethod() - { - return Mod10(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10ULongBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10ULongBenchmarks.cs deleted file mode 100644 index 57cc66f..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod10ULongBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// 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). - /// - [Benchmark(Baseline = true)] - public ulong LegacyModulo() - { - return Value % 10UL; - } - - /// - /// 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`. - /// - [Benchmark] - public ulong ModMethodModulo() - { - return Value.Mod10(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3Benchmarks.cs index 772aef2..5547dd2 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3Benchmarks.cs @@ -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 GetValues() => ValueCases; /// - /// 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. /// [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); } /// - /// 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. /// [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 } /// - /// 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. /// [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); } + + /// + /// Legacy property-based helper; 2.35-2.46 ns at small values and up to 2.46 ns near ulong.Max. + /// + [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); + } + + /// + /// 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. + /// + [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); + } + + /// + /// Legacy step-per-bit helper; 11.9-13.4 ns in the original suite and remains the slowest option. + /// + [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); + } + + /// + /// Step helper exposed via ; 3.58-3.77 ns previously, delivering 3.2-3.6× speedups over the legacy step path. + /// + [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; + } } diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3PropertiesBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3PropertiesBenchmarks.cs deleted file mode 100644 index d106fa4..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3PropertiesBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// 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. - /// - [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); - } - - /// - /// Method variant that routes through ; 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. - /// - [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); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3StepsBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3StepsBenchmarks.cs deleted file mode 100644 index 2998135..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3StepsBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// 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. - /// - [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); - } - - /// - /// Optimized step helper exposed on ; 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. - /// - [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); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod11Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod11Benchmarks.cs new file mode 100644 index 0000000..7b69195 --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/Mod11Benchmarks.cs @@ -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 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; + } + + /// + /// `% 11` baseline across the consolidated dataset; near-empty-loop costs on uint, 0.23-0.27 ns on ulong, and + /// 2.85-2.87 ns on inputs. + /// + [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)), + }; + } + + /// + /// Looping helpers; slower than `%` on uint and ulong but ~4.2× faster on thanks to digit + /// folding. + /// + [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)), + }; + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod11UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod11UInt128Benchmarks.cs deleted file mode 100644 index 0b081d9..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod11UInt128Benchmarks.cs +++ /dev/null @@ -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; } - - /// - /// Direct `% 11` for ; 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). - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return (ulong)(Value % 11UL); - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Value.Mod11(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod11UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod11UIntBenchmarks.cs deleted file mode 100644 index a694547..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod11UIntBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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). - /// - [Benchmark(Baseline = true)] - public uint ModuloOperator() - { - return Value % 11U; - } - - /// - /// 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. - /// - [Benchmark] - public uint ExtensionMethod() - { - return Mod11(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod11ULongBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod11ULongBenchmarks.cs deleted file mode 100644 index 1d80e91..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod11ULongBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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. - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return Value % 11UL; - } - - /// - /// 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. - /// - [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; - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod128Benchmarks.cs new file mode 100644 index 0000000..c2d0bd1 --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/Mod128Benchmarks.cs @@ -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 GetCases() => Cases; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Mod128(uint value) => value & 127U; + + /// + /// `% 128` baseline; sits at 0.01-0.03 ns on uint and ulong, and 2.95-3.42 ns on inputs. + /// + [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)), + }; + } + + /// + /// Bitmask helpers; usually match `%` on 32-bit inputs, are faster on ulong with the exception of the 4,294,966,784 + /// outlier, and provide 150-1000× wins on . + /// + [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)), + }; + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod128UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod128UInt128Benchmarks.cs deleted file mode 100644 index e399757..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod128UInt128Benchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 128` baseline for ; 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). - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return (ulong)(Value % 128UL); - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Value.Mod128(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod128UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod128UIntBenchmarks.cs deleted file mode 100644 index d5d1de2..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod128UIntBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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). - /// - [Benchmark(Baseline = true)] - public uint ModuloOperator() - { - return Value % 128U; - } - - /// - /// 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). - /// - [Benchmark] - public uint ExtensionMethod() - { - return Mod128(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod128ULongBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod128ULongBenchmarks.cs deleted file mode 100644 index 721636f..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod128ULongBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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). - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return Value % 128UL; - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Value.Mod128(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod3Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod3Benchmarks.cs new file mode 100644 index 0000000..32e0513 --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/Mod3Benchmarks.cs @@ -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 GetCases() => Cases; + + /// + /// `% 3` baseline across all widths; previously landed at 0.03-0.09 ns on uint, 0.26-0.49 ns on ulong, + /// and 2.88-2.97 ns on inputs throughout the merged dataset. + /// + [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)), + }; + } + + /// + /// Extension helpers that fold operands by width; they matched the modulo noise floor on uint, trailed by + /// ~1.5-1.7× on ulong, and delivered a 6.4× speedup on . + /// + [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)), + }; + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod3UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod3UInt128Benchmarks.cs deleted file mode 100644 index 1228fba..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod3UInt128Benchmarks.cs +++ /dev/null @@ -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; } - - /// - /// Direct `% 3` on ; 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). - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return (ulong)(Value % 3UL); - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Value.Mod3(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod3UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod3UIntBenchmarks.cs deleted file mode 100644 index 61b7a75..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod3UIntBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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. - /// - [Benchmark(Baseline = true)] - public uint ModuloOperator() - { - return Value % 3U; - } - - /// - /// 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. - /// - [Benchmark] - public uint ExtensionMethod() - { - return UIntExtensions.Mod3(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod3ULongBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod3ULongBenchmarks.cs deleted file mode 100644 index ee68649..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod3ULongBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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. - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return Value % 3UL; - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Mod3(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod5Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod5Benchmarks.cs new file mode 100644 index 0000000..129532e --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/Mod5Benchmarks.cs @@ -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 GetCases() => Cases; + + /// + /// `% 5` baseline; maintained 0.03-0.12 ns on uint, 0.26-0.27 ns on ulong, and 2.84-2.93 ns on + /// values across the unified dataset. + /// + [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)), + }; + } + + /// + /// Extension helpers; effectively tied with modulo on uint, about 1.6-1.7× slower on ulong, and roughly 6× + /// faster than `%` on inputs. + /// + [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)), + }; + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod5UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod5UInt128Benchmarks.cs deleted file mode 100644 index 5845af2..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod5UInt128Benchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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). - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return (ulong)(Value % 5UL); - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Value.Mod5(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod5UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod5UIntBenchmarks.cs deleted file mode 100644 index bcc42bd..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod5UIntBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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). - /// - [Benchmark(Baseline = true)] - public uint ModuloOperator() - { - return Value % 5U; - } - - /// - /// 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). - /// - [Benchmark] - public uint ExtensionMethod() - { - return Value.Mod5(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod5ULongBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod5ULongBenchmarks.cs deleted file mode 100644 index a5536be..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod5ULongBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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. - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return Value % 5UL; - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Mod5(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod6Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod6Benchmarks.cs new file mode 100644 index 0000000..34c64ad --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/Mod6Benchmarks.cs @@ -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 GetCases() => Cases; + + public static IEnumerable GetUInt64Cases() => UInt64Cases; + + public static IEnumerable 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))]; + + /// + /// `% 6` baseline across the merged scalar dataset; previously timed at 0.03-0.05 ns on uint, 0.26-0.27 ns on + /// ulong, and 2.87-2.91 ns on inputs. + /// + [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)), + }; + } + + /// + /// Division-based scalar helpers where available; matches the modulo cost on ulong inputs. + /// + [Benchmark] + [ArgumentsSource(nameof(GetUInt64Cases))] + public ulong DivisionBased(ModBenchmarkCase data) + { + ulong value = (ulong)data.Value; + ulong quotient = value / Divisor; + return value - quotient * Divisor; + } + + /// + /// FastDivHigh scalar helper for ulong; remains 1.0-1.6× slower than the `%` baseline across the dataset. + /// + [Benchmark] + [ArgumentsSource(nameof(GetUInt64Cases))] + public ulong FastDivHigh(ModBenchmarkCase data) + { + ulong value = (ulong)data.Value; + ulong quotient = value.FastDiv64(Divisor, FastDivMul); + return value - quotient * Divisor; + } + + /// + /// Lookup helpers; significantly slower on uint and ulong but 2.4-2.5× faster on . + /// + [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)), + }; + } + + /// + /// `% 6` baseline inside the 10,000,000-iteration loop; measured 1.02-1.04 ns depending on the starting value. + /// + [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; + } + + /// + /// Division-based loop helper; mirrors the modulo timings with small deviations across the dataset. + /// + [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; + } + + /// + /// FastDivHigh loop helper; remained ~1.6× slower than the modulo baseline across the looped dataset. + /// + [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; + } + + /// + /// Lookup-based loop helper; effectively matched the modulo baseline while removing the `%` instruction. + /// + [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; + } + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod6ComparisonBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod6ComparisonBenchmarks.cs deleted file mode 100644 index 7aa1957..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod6ComparisonBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 6` baseline inside the loop; measured 1.02 ns at StartValue 3, 1.035 ns at 131071, and 1.011 ns near ulong.Max. - /// - [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; - } - - /// - /// `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. - /// - [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; - } - - /// - /// 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 `%`. - /// - [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; - } - - /// - /// 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. - /// - [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; - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod6UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod6UInt128Benchmarks.cs deleted file mode 100644 index b5dfaa5..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod6UInt128Benchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 6` baseline; recorded 2.89-2.91 ns on the smaller values and 2.874 ns at 2147483647, 2.908 ns near ulong.Max. - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return (ulong)(Value % 6UL); - } - - /// - /// Extension helper (); 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Value.Mod6(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod6UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod6UIntBenchmarks.cs deleted file mode 100644 index 0043999..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod6UIntBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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. - /// - [Benchmark(Baseline = true)] - public uint ModuloOperator() - { - return Value % 6U; - } - - /// - /// 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. - /// - [Benchmark] - public uint ExtensionMethod() - { - return Mod6(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod6ULongBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod6ULongBenchmarks.cs deleted file mode 100644 index f75a5e2..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod6ULongBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 6` baseline; recorded 0.264 ns at Value 3, 0.262 ns at 131071, and 0.271 ns near ulong.Max. - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return Value % 6UL; - } - - /// - /// Division-based reduction (`value / 6`); timings were 0.277 ns, 0.284 ns, and 0.287 ns across the three start values. - /// - [Benchmark] - public ulong DivisionBased() - { - ulong quotient = Value / 6UL; - return Value - quotient * 6UL; - } - - /// - /// FastDivHigh helper; significantly slower at 1.24 ns (Value 3), 1.04 ns (131071), and 1.17 ns (near ulong.Max). - /// - [Benchmark] - public ulong FastDivHigh() - { - ulong quotient = Value.FastDiv64(6UL, (ulong)(((UInt128)1 << 64) / 6UL)); - return Value - quotient * 6UL; - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Mod6(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod7Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod7Benchmarks.cs new file mode 100644 index 0000000..5f06178 --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/Mod7Benchmarks.cs @@ -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 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; + } + + /// + /// `% 7` baseline across the merged dataset; hovered near the noise floor on uint, 0.27 ns on ulong, and + /// 2.86-2.91 ns on inputs. + /// + [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)), + }; + } + + /// + /// Extension helpers; dramatically slower on uint, modestly slower on ulong, and 3.2-4.2× faster on + /// inputs where the folding helpers shine. + /// + [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)), + }; + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod7UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod7UInt128Benchmarks.cs deleted file mode 100644 index 196a612..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod7UInt128Benchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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). - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return (ulong)(Value % 7UL); - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Value.Mod7(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod7UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod7UIntBenchmarks.cs deleted file mode 100644 index 36d42fa..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod7UIntBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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). - /// - [Benchmark(Baseline = true)] - public uint ModuloOperator() - { - return Value % 7U; - } - - /// - /// 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. - /// - [Benchmark] - public uint ExtensionMethod() - { - return Mod7(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod7ULongBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod7ULongBenchmarks.cs deleted file mode 100644 index d40f4e9..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod7ULongBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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. - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return Value % 7UL; - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Mod7(Value); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod8Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod8Benchmarks.cs new file mode 100644 index 0000000..45008e1 --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/Mod8Benchmarks.cs @@ -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 GetCases() => Cases; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Mod8(ulong value) + { + return value & 7UL; + } + + /// + /// `% 8` baseline across the merged dataset; effectively at the noise floor on uint and ulong, and 2.86-2.89 ns + /// on inputs. + /// + [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)), + }; + } + + /// + /// Bitmask helpers; match `%` on 32-bit inputs, are typically faster on ulong, and deliver 140-280× wins on + /// by avoiding division. + /// + [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)), + }; + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod8UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod8UInt128Benchmarks.cs deleted file mode 100644 index 2eb66b2..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod8UInt128Benchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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). - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return (ulong)(Value % 8UL); - } - - /// - /// 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. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Value.Mod8(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod8UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod8UIntBenchmarks.cs deleted file mode 100644 index 6d6aa1d..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod8UIntBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// `% 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. - /// - [Benchmark(Baseline = true)] - public uint ModuloOperator() - { - return Value % 8U; - } - - /// - /// 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. - /// - [Benchmark] - public uint ExtensionMethod() - { - return Value.Mod8(); - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/Mod8ULongBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod8ULongBenchmarks.cs deleted file mode 100644 index c3d1e5a..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Mod8ULongBenchmarks.cs +++ /dev/null @@ -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; } - - /// - /// Uses the built-in % 8; 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. - /// - [Benchmark(Baseline = true)] - public ulong ModuloOperator() - { - return Value % 8UL; - } - - /// - /// 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 % once the divisor - /// approaches 264. - /// - [Benchmark] - public ulong ExtensionMethod() - { - return Mod8(Value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Mod8(ulong value) - { - return value & 7UL; - } -} diff --git a/EvenPerfectBitScanner.Benchmarks/ModBenchmarkCase.cs b/EvenPerfectBitScanner.Benchmarks/ModBenchmarkCase.cs new file mode 100644 index 0000000..1f77f22 --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/ModBenchmarkCase.cs @@ -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; + } +} diff --git a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModBenchmarks.cs index a1f2e3c..3c5680f 100644 --- a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModBenchmarks.cs @@ -1,3 +1,5 @@ +using System; +using System.Buffers.Binary; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using System.Numerics; @@ -5,25 +7,47 @@ using PerfectNumbers.Core; namespace EvenPerfectBitScanner.Benchmarks; +/// +/// 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) +/// [MemoryDiagnoser] [SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)] public class Pow2MontgomeryModBenchmarks { 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(13); + private readonly Random _heuristicRandom = new(113); + private ulong? _previousPrimeOrder; + private UInt128? _previousWidePrimeOrder; public enum InputScale { Small, - Large + Large, + VeryLarge } [ParamsAllValues] @@ -34,6 +58,9 @@ public class Pow2MontgomeryModBenchmarks { 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(); @@ -43,6 +70,19 @@ public class Pow2MontgomeryModBenchmarks _largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus); _largeExponents[i] = NextLargeExponent(); _largeCycles[i] = 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); } } @@ -114,6 +154,89 @@ public class Pow2MontgomeryModBenchmarks return checksum; } + /// + /// 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). + /// + // [Benchmark] + public ulong MontgomeryWithGpuCycleComputationCpu() + { + GetData(out var exponents, out var 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; + } + + /// + /// 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). + /// + // [Benchmark] + public ulong MontgomeryWithGpuCycleComputationGpu() + { + GetData(out var exponents, out var 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 var exponents, out var 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 var exponents, out var 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; + } + /// /// 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×). /// @@ -218,23 +341,39 @@ public class Pow2MontgomeryModBenchmarks private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles) { - if (Scale == InputScale.Small) + switch (Scale) { - exponents = _smallExponents; - divisors = _smallDivisors; - cycles = _smallCycles; - } - else - { - exponents = _largeExponents; - divisors = _largeDivisors; - cycles = _largeCycles; + 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(1L << 60, long.MaxValue); + private ulong NextLargeExponent() => (ulong)_random.NextInt64(OneShiftedLeft60, long.MaxValue); + + private ulong NextVeryLargeExponent() + { + Span buffer = stackalloc byte[8]; + _heuristicRandom.NextBytes(buffer); + ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63); + return value; + } private ulong NextSmallOddModulus() { @@ -249,6 +388,27 @@ public class Pow2MontgomeryModBenchmarks return (modulus, (ulong)bitLength); } + private ulong NextVeryLargeOddModulus() + { + Span buffer = stackalloc byte[8]; + _heuristicRandom.NextBytes(buffer); + ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL; + return value; + } + + private UInt128 NextWideOddModulus() + { + Span buffer = stackalloc byte[16]; + _heuristicRandom.NextBytes(buffer); + + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer); + Span 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) @@ -269,6 +429,64 @@ public class Pow2MontgomeryModBenchmarks 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); + } + +#if DEBUG + Console.WriteLine("Trying heuristic. Prime order calculation"); +#endif + ulong order = PrimeOrderCalculator.Calculate( + modulus, + _previousPrimeOrder, + divisorData, + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + + 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) @@ -510,4 +728,3 @@ public class Pow2MontgomeryModBenchmarks return (ulong)(product % modulus); } } - diff --git a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModCycleComputationBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModCycleComputationBenchmarks.cs deleted file mode 100644 index 8720256..0000000 --- a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModCycleComputationBenchmarks.cs +++ /dev/null @@ -1,410 +0,0 @@ -using System.Buffers.Binary; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; -using PerfectNumbers.Core; - -namespace EvenPerfectBitScanner.Benchmarks; - -/// -/// 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) -/// -[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); - } - } - - /// - /// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the - /// large set. - /// - // [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; - } - - /// - /// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the - /// large set. - /// - // [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; - } - - /// - /// 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×). - /// - [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; - } - - /// - /// 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×). - /// - [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; - } - - /// - /// 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). - /// - // [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; - } - - /// - /// 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). - /// - // [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 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 buffer = stackalloc byte[8]; - _random.NextBytes(buffer); - ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL; - return value; - } - - private UInt128 NextWideOddModulus() - { - Span buffer = stackalloc byte[16]; - _random.NextBytes(buffer); - ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer); - Span 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.Gpu); - - 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); - } -} - diff --git a/EvenPerfectBitScanner.Benchmarks/Program.cs b/EvenPerfectBitScanner.Benchmarks/Program.cs index 7c70f11..c42d0dd 100644 --- a/EvenPerfectBitScanner.Benchmarks/Program.cs +++ b/EvenPerfectBitScanner.Benchmarks/Program.cs @@ -9,9 +9,9 @@ public static class Program // var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator); // BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); - Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new() + Pow2MontgomeryModBenchmarks benchmarks = new() { - Scale = Pow2MontgomeryModCycleComputationBenchmarks.InputScale.Large + Scale = Pow2MontgomeryModBenchmarks.InputScale.Large }; GpuPrimeWorkLimiter.SetLimit(1);