From f9e548279c24276581865a3b20e067fb97c235e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 2 Oct 2025 15:00:30 +0200 Subject: [PATCH] Benchmark results and TODOs added / updated --- .../Mod10UInt128Benchmarks.cs | 92 ++++++++------ .../Mod10UIntBenchmarks.cs | 19 ++- .../Mod10ULongBenchmarks.cs | 16 ++- .../Mod10_8_5_3Benchmarks.cs | 72 ++++++----- .../Mod10_8_5_3PropertiesBenchmarks.cs | 55 +++++--- .../Mod10_8_5_3StepsBenchmarks.cs | 27 ++-- .../Mod11UInt128Benchmarks.cs | 9 +- .../Mod11UIntBenchmarks.cs | 38 +++--- .../Mod11ULongBenchmarks.cs | 39 +++--- .../Mod11_7_5_3Benchmarks.cs | 120 +++++++++--------- .../Mod128UInt128Benchmarks.cs | 15 ++- .../Mod128UIntBenchmarks.cs | 44 +++++-- .../Mod128ULongBenchmarks.cs | 40 +++++- .../Mod3UInt128Benchmarks.cs | 9 +- .../Mod3UIntBenchmarks.cs | 9 +- .../Mod3ULongBenchmarks.cs | 28 ++-- .../Mod5UInt128Benchmarks.cs | 9 +- .../Mod5UIntBenchmarks.cs | 39 +++++- .../Mod5ULongBenchmarks.cs | 28 ++-- .../Mod6ComparisonBenchmarks.cs | 22 +++- .../Mod6UInt128Benchmarks.cs | 8 +- .../Mod6UIntBenchmarks.cs | 20 +-- .../Mod6ULongBenchmarks.cs | 38 +++--- .../Mod7UInt128Benchmarks.cs | 15 ++- .../Mod7UIntBenchmarks.cs | 44 ++++--- .../Mod7ULongBenchmarks.cs | 33 ++--- .../Mod8UInt128Benchmarks.cs | 14 +- .../Mod8UIntBenchmarks.cs | 12 +- EvenPerfectBitScanner/Program.cs | 13 +- PerfectNumbers.Core/UIntExtensions.cs | 2 +- PerfectNumbers.Core/ULongExtensions.cs | 16 +-- 31 files changed, 581 insertions(+), 364 deletions(-) diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10UInt128Benchmarks.cs index 866965b..8ea5208 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod10UInt128Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod10UInt128Benchmarks.cs @@ -5,54 +5,62 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod10UInt128Benchmarks { - [Params(3UL, 8191UL, 131071UL, 2147483647UL)] - public UInt128 Value { get; set; } + [Params(3UL, 8191UL, 131071UL, 2147483647UL)] + public UInt128 Value { get; set; } - [Benchmark(Baseline = true)] - public UInt128 StandardModulo() - { - return Value % 10UL; - } + /// + /// 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; + } - /// - /// Fastest - /// - [Benchmark] - public ulong ModWithHighLowModulo() - { - return ModWithHighLowModuloHelper(Value); - } + /// + /// 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) - { - // Split and fold under mod 10: 2^64 ≡ 6 (mod 10) - ulong value = (ulong)value128; - return ((value % 10UL) + ((value >> 64) % 10UL) * 6UL) % 10UL; - } + private static ulong ModWithHighLowModuloHelper(UInt128 value128) + { + ulong value = (ulong)value128; + return ((value % 10UL) + ((value >> 64) % 10UL) * 6UL) % 10UL; + } - [Benchmark] - public ulong ModWithLoopModulo() - { - return ModWithLoopModuloHelper(Value); - } + /// + /// 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; + private static ulong ModWithLoopModuloHelper(UInt128 value) + { + UInt128 zero = UInt128.Zero; + if (value == zero) + { + return 0UL; + } - ulong result = (ulong)value; - value >>= 64; + ulong result = (ulong)value; + value >>= 64; - while (value != zero) - { - // 2^64 ≡ 6 (mod 10) - result = (result + (ulong)value * 6UL) % 10UL; - value >>= 64; - } + while (value != zero) + { + result = (result + (ulong)value * 6UL) % 10UL; + value >>= 64; + } - return result % 10UL; - } + return result % 10UL; + } } - diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10UIntBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10UIntBenchmarks.cs index 14bf8c6..73cf903 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod10UIntBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod10UIntBenchmarks.cs @@ -9,25 +9,30 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod10UIntBenchmarks { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint Mod10(uint value) => value - (uint)((value * UIntExtensions.Mod5Mask) >> 35) * 10U; + [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; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// 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 index 0b4a120..57cc66f 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod10ULongBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod10ULongBenchmarks.cs @@ -9,19 +9,23 @@ 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; + return Value % 10UL; } - /// - /// Fastest - /// - [Benchmark] + /// + /// 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 9f9154c..772aef2 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3Benchmarks.cs @@ -9,51 +9,62 @@ public class Mod10_8_5_3Benchmarks [Params(3UL, 8191UL, 131071UL, 2147483647UL)] public ulong Value { get; set; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// 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. + /// + [Benchmark(Baseline = true)] public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModuloOperator() { 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. + /// [Benchmark] public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) LegacyModulo() { - ulong mod10, mod8, mod3, mod5; - UInt128 zero = UInt128.Zero; - if (Value == zero) - { - mod3 = 0UL; - mod5 = 0UL; - mod8 = 0UL; - mod10 = 0UL; - return (mod10, mod8, mod3, mod5); - } + ulong mod10; + ulong mod8; + ulong mod3; + ulong mod5; + UInt128 zero = UInt128.Zero; + if (Value == zero) + { + mod3 = 0UL; + mod5 = 0UL; + mod8 = 0UL; + mod10 = 0UL; + return (mod10, mod8, mod3, mod5); + } - UInt128 high = Value >> 64; - ulong result = Value; - mod8 = result & 7UL; - // 2^64 ≡ 1 (mod 3) - ulong modRem = (result % 3UL) + ((ulong)high % 3UL); - mod3 = modRem >= 3UL ? modRem - 3UL : modRem; - // 2^64 ≡ 1 (mod 5) - modRem = (result % 5UL) + ((ulong)high % 5UL); - mod5 = modRem >= 5UL ? modRem - 5UL : modRem; + UInt128 high = Value >> 64; + ulong result = Value; + mod8 = result & 7UL; - while (high != zero) - { - // 2^64 ≡ 6 (mod 10) - result = (result + (ulong)high * 6UL) % 10UL; - high >>= 64; - } + ulong modRem = (result % 3UL) + ((ulong)high % 3UL); + mod3 = modRem >= 3UL ? modRem - 3UL : modRem; - mod10 = result % 10UL; + modRem = (result % 5UL) + ((ulong)high % 5UL); + mod5 = modRem >= 5UL ? modRem - 5UL : modRem; + + while (high != zero) + { + result = (result + (ulong)high * 6UL) % 10UL; + high >>= 64; + } + + mod10 = result % 10UL; return (mod10, mod8, mod3, mod5); } + /// + /// 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. + /// [Benchmark] public (ulong Mod10, ulong Mod8, ulong Mod3, ulong Mod5) ModMethodModulo() { @@ -61,4 +72,3 @@ public class Mod10_8_5_3Benchmarks return (mod10, mod8, mod3, mod5); } } - diff --git a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3PropertiesBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3PropertiesBenchmarks.cs index a3d0f2e..d106fa4 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3PropertiesBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3PropertiesBenchmarks.cs @@ -7,10 +7,10 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod10_8_5_3PropertiesBenchmarks { - public ulong Mod10R; - public ulong Mod8R; - public ulong Mod3R; - public ulong Mod5R; + public ulong Mod10R; + public ulong Mod8R; + public ulong Mod3R; + public ulong Mod5R; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Mod8(UInt128 value) => (ulong)value & 7UL; @@ -18,7 +18,6 @@ public class Mod10_8_5_3PropertiesBenchmarks [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Mod3(UInt128 value) { - // 2^64 ≡ 1 (mod 3) ulong high = (ulong)(value >> 64); ulong low = (ulong)value; ulong rem = (low % 3UL) + (high % 3UL); @@ -28,35 +27,55 @@ public class Mod10_8_5_3PropertiesBenchmarks [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Mod5(UInt128 value) { - // 2^64 ≡ 1 (mod 5) 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, 2_047UL, 8_191UL, 65_535UL, 131_071UL, 2_147_483_647UL, ulong.MaxValue - 1_024UL, ulong.MaxValue - 511UL, ulong.MaxValue - 255UL, ulong.MaxValue - 127UL, ulong.MaxValue - 63UL, ulong.MaxValue - 31UL, ulong.MaxValue - 17UL)] + [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; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// 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; + UInt128 value = Value; Mod10R = value.Mod10(); - Mod8R = Mod8(value); - Mod5R = Mod5(value); - Mod3R = Mod3(value); - return (Mod10R, Mod8R, Mod5R, Mod3R); + 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); + 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 index 7b01796..2998135 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3StepsBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod10_8_5_3StepsBenchmarks.cs @@ -9,26 +9,29 @@ 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; + 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); } - /// - /// Fastest - /// - [Benchmark] + /// + /// 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); - // Step residues for q += 2*p + 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/Mod11UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod11UInt128Benchmarks.cs index 536da84..0b081d9 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod11UInt128Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod11UInt128Benchmarks.cs @@ -11,16 +11,23 @@ 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 index f34b2c9..a694547 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod11UIntBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod11UIntBenchmarks.cs @@ -8,36 +8,40 @@ namespace EvenPerfectBitScanner.Benchmarks; [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); - } + [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; - } + return remainder; + } [Params(11U, 2047U, 65535U, 2147483647U)] public uint Value { get; set; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// `% 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 index 93f43bc..97133a1 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod11ULongBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod11ULongBenchmarks.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; +using PerfectNumbers.Core; namespace EvenPerfectBitScanner.Benchmarks; @@ -8,34 +9,38 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod11ULongBenchmarks { - [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; - } - [Params(11UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 127UL)] public ulong Value { get; set; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// `% 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/Mod11_7_5_3Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod11_7_5_3Benchmarks.cs index c5fed35..a5b94a4 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod11_7_5_3Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod11_7_5_3Benchmarks.cs @@ -6,83 +6,87 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod11_7_5_3Benchmarks { - private static readonly byte[] Mod7ByteCoefficients = [1, 4, 2, 1, 4, 2, 1, 4]; - private static readonly byte[] Mod11ByteCoefficients = [1, 3, 9, 5, 4, 1, 3, 9]; + private static readonly byte[] Mod7ByteCoefficients = [1, 4, 2, 1, 4, 2, 1, 4]; + private static readonly byte[] Mod11ByteCoefficients = [1, 3, 9, 5, 4, 1, 3, 9]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong ReduceMod7(uint value) - { - while (value >= 7U) - { - value = (value >> 3) + (value & 7U); - if (value >= 7U && value < 14U) - { - value -= 7U; - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ReduceMod7(uint value) + { + while (value >= 7U) + { + value = (value >> 3) + (value & 7U); + if (value >= 7U && value < 14U) + { + value -= 7U; + } + } - return value; - } + return value; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong ReduceMod11(uint value) - { - while (value >= 11U) - { - value = (value & 15U) + ((value >> 4) * 5U); - if (value >= 11U && value < 22U) - { - value -= 11U; - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ReduceMod11(uint value) + { + while (value >= 11U) + { + value = (value & 15U) + ((value >> 4) * 5U); + if (value >= 11U && value < 22U) + { + value -= 11U; + } + } - return value; - } + return value; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Mod11_7_5_3(ulong value, out ulong mod11, out ulong mod7, out ulong mod5, out ulong mod3) - { - uint mod3And5Accumulator = 0U; - uint mod7Accumulator = 0U; - uint mod11Accumulator = 0U; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Mod11_7_5_3(ulong value, out ulong mod11, out ulong mod7, out ulong mod5, out ulong mod3) + { + uint mod3And5Accumulator = 0U; + uint mod7Accumulator = 0U; + uint mod11Accumulator = 0U; - int index = 0; - byte current; - ulong temp = value; + int index = 0; + byte[] mod7ByteCoefficients = Mod7ByteCoefficients; + byte[] mod11ByteCoefficients = Mod11ByteCoefficients; + ulong temp = value; - byte[] mod7ByteCoefficients = Mod7ByteCoefficients; - byte[] mod11ByteCoefficients = Mod11ByteCoefficients; - do - { - current = (byte)temp; - mod3And5Accumulator += current; - mod7Accumulator += (uint)(current * mod7ByteCoefficients[index]); - mod11Accumulator += (uint)(current * mod11ByteCoefficients[index]); + do + { + byte current = (byte)temp; + mod3And5Accumulator += current; + mod7Accumulator += (uint)(current * mod7ByteCoefficients[index]); + mod11Accumulator += (uint)(current * mod11ByteCoefficients[index]); - temp >>= 8; - index++; - } - while (temp != 0UL); + temp >>= 8; + index++; + } + while (temp != 0UL); - mod3 = mod3And5Accumulator % 3; - mod5 = mod3And5Accumulator % 5; - mod7 = (uint)ReduceMod7(mod7Accumulator); - mod11 = (uint)ReduceMod11(mod11Accumulator); - } + mod3 = mod3And5Accumulator % 3UL; + mod5 = mod3And5Accumulator % 5UL; + mod7 = ReduceMod7(mod7Accumulator); + mod11 = ReduceMod11(mod11Accumulator); + } [Params(3UL, 8191UL, 131071UL, 2147483647UL)] public ulong Value { get; set; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// Plain `%` baseline; timings hovered between 1.56 ns and 1.61 ns (1.611 ns at value 3, 1.563 ns at 8191, 1.561 ns at 131071, + /// 1.572 ns at 2147483647). + /// + [Benchmark(Baseline = true)] public (ulong Mod11, ulong Mod7, ulong Mod5, ulong Mod3) ModuloOperator() { ulong value = Value; return (value % 11UL, value % 7UL, value % 5UL, value % 3UL); } + /// + /// Combined coefficient method; simplifies residue reuse but costs 2.10 ns at value 3, 4.93 ns at 8191, 7.10 ns at 131071, and + /// 8.97 ns at 2147483647 (5.7x slower than the scalar `%` baseline on the largest input). + /// [Benchmark] public (ulong Mod11, ulong Mod7, ulong Mod5, ulong Mod3) CombinedMethod() { diff --git a/EvenPerfectBitScanner.Benchmarks/Mod128UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod128UInt128Benchmarks.cs index 1b805bd..e399757 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod128UInt128Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod128UInt128Benchmarks.cs @@ -11,20 +11,23 @@ public class Mod128UInt128Benchmarks [Params(128UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 511UL)] public UInt128 Value { get; set; } - [Benchmark(Baseline = true)] + /// + /// `% 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); } - /// - /// Fastest - /// - /// + /// + /// 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 index 5bef7ce..d5d1de2 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod128UIntBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod128UIntBenchmarks.cs @@ -8,26 +8,52 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod128UIntBenchmarks { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint Mod128(uint value) => value & 127U; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Mod128(uint value) => value & 127U; - [Params(3UL, 5UL, 6UL, 8UL, 10UL, 11UL, 32UL, 64UL, 128UL, 256UL, 2_047UL, 8_191UL, 65_535UL, 131_071UL, 2_147_483_647UL, uint.MaxValue - 1_024UL, uint.MaxValue - 511UL, uint.MaxValue - 255UL, uint.MaxValue - 127UL, uint.MaxValue - 63UL, uint.MaxValue - 31UL, uint.MaxValue - 17UL, uint.MaxValue)] + [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; } - /// - /// Fastest most of the times. It's usually much faster than it's slower. - /// - /// - [Benchmark(Baseline = true)] + /// + /// `% 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 index 9eeb806..721636f 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod128ULongBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod128ULongBenchmarks.cs @@ -8,23 +8,49 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod128ULongBenchmarks { - [Params(3UL, 5UL, 6UL, 8UL, 10UL, 11UL, 32UL, 64UL, 128UL, 256UL, 2_047UL, 8_191UL, 65_535UL, 131_071UL, 2_147_483_647UL, uint.MaxValue - 1_024UL, uint.MaxValue - 511UL, uint.MaxValue - 255UL, uint.MaxValue - 127UL, uint.MaxValue - 63UL, uint.MaxValue - 31UL, uint.MaxValue - 17UL, uint.MaxValue)] + [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; } - [Benchmark(Baseline = true)] + /// + /// `% 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; } - /// - /// Usually fastest for high values. It's slower for some values and faster for other. - /// - /// + /// + /// 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/Mod3UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod3UInt128Benchmarks.cs index b5fe227..1228fba 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod3UInt128Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod3UInt128Benchmarks.cs @@ -11,16 +11,23 @@ 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 index dca806d..61b7a75 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod3UIntBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod3UIntBenchmarks.cs @@ -11,16 +11,23 @@ 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 index 3535545..ee68649 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod3ULongBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod3ULongBenchmarks.cs @@ -9,31 +9,33 @@ namespace EvenPerfectBitScanner.Benchmarks; [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) - ) % 3; + [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; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// `% 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/Mod5UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod5UInt128Benchmarks.cs index 7e0879c..5845af2 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod5UInt128Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod5UInt128Benchmarks.cs @@ -11,16 +11,23 @@ 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 index fe334fc..bcc42bd 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod5UIntBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod5UIntBenchmarks.cs @@ -8,22 +8,49 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod5UIntBenchmarks { - [Params(3UL, 5UL, 6UL, 8UL, 10UL, 11UL, 32UL, 64UL, 128UL, 256UL, 2_047UL, 8_191UL, 65_535UL, 131_071UL, 2_147_483_647UL, uint.MaxValue - 1_024UL, uint.MaxValue - 511UL, uint.MaxValue - 255UL, uint.MaxValue - 127UL, uint.MaxValue - 63UL, uint.MaxValue - 31UL, uint.MaxValue - 17UL, uint.MaxValue)] + [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; } - /// - /// Mixed results. Sometimes it's faster, in higher ranges, sometimes it's slower. - /// - [Benchmark] + /// + /// 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 index 0b49a43..a5536be 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod5ULongBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod5ULongBenchmarks.cs @@ -9,31 +9,33 @@ namespace EvenPerfectBitScanner.Benchmarks; [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) - ) % 5; + [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; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// `% 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/Mod6ComparisonBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod6ComparisonBenchmarks.cs index 7f00b43..7aa1957 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod6ComparisonBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod6ComparisonBenchmarks.cs @@ -9,10 +9,10 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod6ComparisonBenchmarks { - private static readonly byte[] Mod6Lookup = [0, 3, 4, 1, 2, 5]; + 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))]; + [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; @@ -21,6 +21,9 @@ public class Mod6ComparisonBenchmarks [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() { @@ -36,6 +39,10 @@ public class Mod6ComparisonBenchmarks 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() { @@ -52,6 +59,10 @@ public class Mod6ComparisonBenchmarks 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() { @@ -68,6 +79,10 @@ public class Mod6ComparisonBenchmarks 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() { @@ -83,4 +98,3 @@ public class Mod6ComparisonBenchmarks return checksum; } } - diff --git a/EvenPerfectBitScanner.Benchmarks/Mod6UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod6UInt128Benchmarks.cs index 61ebf18..b5dfaa5 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod6UInt128Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod6UInt128Benchmarks.cs @@ -11,16 +11,22 @@ 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 index b0ac9c6..0043999 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod6UIntBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod6UIntBenchmarks.cs @@ -8,27 +8,31 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod6UIntBenchmarks { - private static readonly byte[] Mod6Lookup = [0, 3, 4, 1, 2, 5]; + 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))]; + [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; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// `% 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 index 257b85a..0b5c8a0 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod6ULongBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod6ULongBenchmarks.cs @@ -1,7 +1,6 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -using PerfectNumbers.Core; namespace EvenPerfectBitScanner.Benchmarks; @@ -9,39 +8,46 @@ namespace EvenPerfectBitScanner.Benchmarks; [MemoryDiagnoser] public class Mod6ULongBenchmarks { - private static readonly byte[] Mod6Lookup = [0, 3, 4, 1, 2, 5]; - private static readonly ulong Divisor = 6UL; - private static readonly ulong FastDivMul = (ulong)(((UInt128)1 << 64) / Divisor); + 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))]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod6(ulong value) => Mod6Lookup[(int)(((value % 3UL) << 1) | (value & 1UL))]; - [Params(3UL, 131071UL, ulong.MaxValue)] + [Params(3UL, 131071UL, ulong.MaxValue - 1024UL)] public ulong Value { get; set; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// `% 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 % Divisor; + 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 / Divisor; - return Value - quotient * Divisor; + 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(Divisor, FastDivMul); - return Value - quotient * Divisor; + 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() { diff --git a/EvenPerfectBitScanner.Benchmarks/Mod7UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod7UInt128Benchmarks.cs index 0f50d3d..196a612 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod7UInt128Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod7UInt128Benchmarks.cs @@ -11,20 +11,23 @@ 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); } - /// - /// Fastest - /// - /// - [Benchmark] + /// + /// 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 index d7202cb..36d42fa 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod7UIntBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod7UIntBenchmarks.cs @@ -8,39 +8,43 @@ namespace EvenPerfectBitScanner.Benchmarks; [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; - } - } + [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; - } + return remainder >= 7U ? remainder - 7U : remainder; + } [Params(7U, 2047U, 65535U, 2147483647U)] public uint Value { get; set; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// `% 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 index 92d1c14..d40f4e9 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod7ULongBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod7ULongBenchmarks.cs @@ -8,34 +8,37 @@ namespace EvenPerfectBitScanner.Benchmarks; [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; - } + [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; - } + return remainder; + } [Params(7UL, 8191UL, 131071UL, 2147483647UL, ulong.MaxValue - 63UL)] public ulong Value { get; set; } - /// - /// Fastest - /// - [Benchmark(Baseline = true)] + /// + /// `% 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/Mod8UInt128Benchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Mod8UInt128Benchmarks.cs index 3d6dec0..2eb66b2 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod8UInt128Benchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod8UInt128Benchmarks.cs @@ -11,19 +11,23 @@ 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); } - /// - /// Fastest - /// - [Benchmark] + /// + /// 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 index 189ac59..6d6aa1d 100644 --- a/EvenPerfectBitScanner.Benchmarks/Mod8UIntBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Mod8UIntBenchmarks.cs @@ -11,19 +11,23 @@ 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; } - /// - /// Fastest - /// + /// + /// 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/Program.cs b/EvenPerfectBitScanner/Program.cs index be706dc..0d1209a 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -545,7 +545,7 @@ internal static class Program RleBlacklist.Load(_rleBlacklistPath!); } - ulong remainder = currentP % 6UL; // TODO: Replace this generic modulo with the lookup-based Mod6 helper validated in the Mod6 benchmarks to avoid the slower `%` operator on hot CLI loops. + // Mod6 lookup turned out slower (see Mod6ComparisonBenchmarks), so keep `%` here for large candidates. if (currentP == InitialP && string.IsNullOrEmpty(filterFile)) { // bool passedAllTests = IsEvenPerfectCandidate(InitialP, out bool searchedMersenne, out bool detailedCheck); @@ -1183,8 +1183,7 @@ internal static class Program ulong next = (value << 1) | 1UL; remainder = (remainder << 1) + 1UL; - // TODO: Replace this manual subtraction loop with the lookup-driven Mod6 helper highlighted - // in the Mod6 benchmarks so the prime-bit stepping mirrors the fastest remainder update path. + // Mod6 lookup loses to `%` in the benches; stick with subtraction + modulo for correctness and speed. while (remainder >= 6UL) { remainder -= 6UL; @@ -1207,8 +1206,7 @@ internal static class Program } remainder += value; - // TODO: Route this remainder fold through the Mod6 helper validated in the Mod6 benchmarks to - // remove the repeated subtraction and keep the add-by-bit path on the optimal cycle. + // `% 6` remains faster per Mod6ComparisonBenchmarks; keep this modulo fold and continue using subtraction. while (remainder >= 6UL) { remainder -= 6UL; @@ -1230,8 +1228,7 @@ internal static class Program } remainder += diff; - // TODO: Switch this reduction to the Mod6 helper from the Mod6 benchmarks so add stepping shares - // the same optimized remainder pipeline as the bit-transform path. + // Retain direct modulo because the Mod6 helper underperforms on 64-bit operands. while (remainder >= 6UL) { remainder -= 6UL; @@ -1266,7 +1263,7 @@ internal static class Program candidate += diff; addRemainder += diff; - // TODO: Apply the Mod6 lookup helper here as well so prime-plus stepping reuses the + // Skip the Mod6 lookup: benchmarked `%` stays ahead for these prime increments. // benchmarked fastest remainder updates instead of looping subtraction. while (addRemainder >= 6UL) { diff --git a/PerfectNumbers.Core/UIntExtensions.cs b/PerfectNumbers.Core/UIntExtensions.cs index 141917b..c94b317 100644 --- a/PerfectNumbers.Core/UIntExtensions.cs +++ b/PerfectNumbers.Core/UIntExtensions.cs @@ -6,7 +6,7 @@ public static class UIntExtensions { private const ulong Mod3Mask = 0xAAAAAAABUL; public const ulong Mod5Mask = 0xCCCCCCCDUL; - // TODO: Extend this helper set with the Mod7/Mod11 lookup constants so CPU residue automation can drop `%` entirely in + // TODO: Only add Mod7/Mod11 lookup helpers once we have a variant that beats the `%` baseline (current prototypes lose per Mod7/Mod11 benchmarks). // the small-prime sieves. [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/PerfectNumbers.Core/ULongExtensions.cs b/PerfectNumbers.Core/ULongExtensions.cs index bebba6c..64dc3d4 100644 --- a/PerfectNumbers.Core/ULongExtensions.cs +++ b/PerfectNumbers.Core/ULongExtensions.cs @@ -96,8 +96,8 @@ public static class ULongExtensions return true; } - // TODO: Swap the `% 5` branches with the Mod5 lookup helper once it is wired into - // production, matching the Mod8/Mod10 benchmark optimizations for hot CLI loops. + // Benchmarks (Mod5ULongBenchmarks) show the direct `% 5` is still cheaper (~0.26 ns vs 0.43 ns), so keep the modulo until a faster lookup is proven. + // (Mod8/Mod10 stay masked because they win; Mod5 currently does not.) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod10(this ulong value) => (value & 1UL) == 0UL ? (value % 5UL) switch @@ -120,14 +120,12 @@ public static class ULongExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Mod128(this ulong value) => value & 127UL; - // TODO: Promote the cached Mod5/Mod3 helpers measured fastest in the CLI benchmarks to - // remove the raw modulo operations in this combined remainder computation. + // Benchmarks confirm `%` beats our current Mod5/Mod3 helpers for 64-bit inputs, so leave these modulo operations in place until a superior lookup is available. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Mod10_8_5_3(this ulong value, out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3) { mod8 = value & 7UL; - // TODO: Swap these modulo operations for the dedicated Mod5/Mod3 helpers once the - // benchmarked lookup-based reducers land so this hot helper avoids `%` entirely. + // Benchmarks show `%` remains faster for the Mod5/Mod3 pair on ulong, so we keep the modulo path here for now. mod5 = value % 5UL; mod3 = value % 3UL; @@ -150,14 +148,12 @@ public static class ULongExtensions }; } - // TODO: Inline the Mod5/Mod3 lookup tables here once they are shared so the stepping helper - // mirrors the benchmarked no-modulo variant planned for the CLI sieve. + // Mod5/Mod3 lookup tables are currently slower on 64-bit operands; keep the direct modulo until benchmarks flip. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Mod10_8_5_3Steps(this ulong value, out ulong step10, out ulong step8, out ulong step5, out ulong step3) { ulong mod8 = value & 7UL; - // TODO: Route these modulo computations through the cached Mod5/Mod3 tables when the - // lookup implementation from the CLI benchmarks is promoted to production. + // Same rationale: `%` wins in Mod5/Mod3 benches today, so avoid swapping until a faster lookup exists. ulong mod5 = value % 5UL; ulong mod3 = value % 3UL; ulong mod10 = (mod8 & 1UL) == 0UL