From df04ba601c06f350e8e27fd8f5dbbab79e7750e4 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 20 Nov 2025 01:43:29 +0100 Subject: [PATCH] Various small optimizations and cleanups. Methods made static, where possible. Duplicated & slow PrimeTester.BinaryGcd implementation removed. Optimized version was used instead. --- .../PrimeTesterBenchmarks.cs | 8 +- .../HeuristicCombinedPrimeTester.cs | 27 +++---- PerfectNumbers.Core/HeuristicPrimeTester.cs | 76 +++++++------------ PerfectNumbers.Core/PrimeTester.cs | 37 +-------- 4 files changed, 43 insertions(+), 105 deletions(-) diff --git a/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs index f9a178b..5dc0110 100644 --- a/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs @@ -28,8 +28,8 @@ public class PrimeTesterBenchmarks PrimeOrderCalculatorAccelerator.WarmUp(); // HeuristicGroupABPrimeTesterAccelerator.WarmUp(); - _ = _heuristicTester.IsPrimeCpu(_candidates[0]); - _ = _heuristicTester.IsPrimeGpu(_candidates[0]); + _ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]); + _ = HeuristicPrimeTester.IsPrimeGpu(_candidates[0]); _ = Prime.Numbers.IsPrime(_candidates[0]); } @@ -42,7 +42,7 @@ public class PrimeTesterBenchmarks for (int i = 0; i < values.Length; i++) { - if (tester.IsPrimeCpu(values[i])) + if (HeuristicPrimeTester.IsPrimeCpu(values[i])) { primeCount++; } @@ -60,7 +60,7 @@ public class PrimeTesterBenchmarks for (int i = 0; i < values.Length; i++) { - if (tester.IsPrimeGpu(values[i])) + if (HeuristicPrimeTester.IsPrimeGpu(values[i])) { primeCount++; } diff --git a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs index 1277bea..7d0be22 100644 --- a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs @@ -3,7 +3,6 @@ using ILGPU.Runtime; using System.Runtime.CompilerServices; using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu.Accelerators; -using ILGPU; namespace PerfectNumbers.Core; @@ -266,14 +265,10 @@ public sealed class HeuristicCombinedPrimeTester [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool EvaluateWithOpenNumericFallback(ulong n) - { - return Prime.Numbers.IsPrime(n); - } - internal static ulong ComputeHeuristicDivisorSquareLimit(ulong n) - { - return n; - } + private static bool EvaluateWithOpenNumericFallback(ulong n) => Prime.Numbers.IsPrime(n); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ulong ComputeHeuristicDivisorSquareLimit(ulong n) => n; private static ReadOnlySpan GetCombinedDivisors(byte nMod10) => nMod10 switch { @@ -470,9 +465,7 @@ public sealed class HeuristicCombinedPrimeTester [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static HeuristicDivisorEnumerator CreateHeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span groupBBuffer) - { - return new HeuristicDivisorEnumerator(maxDivisorSquare, nMod10, groupBBuffer); - } + => new(maxDivisorSquare, nMod10, groupBBuffer); internal struct HeuristicGroupBSequenceState { @@ -480,9 +473,7 @@ public sealed class HeuristicCombinedPrimeTester [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static MersenneHeuristicDivisorEnumerator CreateMersenneDivisorEnumerator(ulong exponent, ulong maxDivisor) - { - return new MersenneHeuristicDivisorEnumerator(exponent, maxDivisor); - } + => new(exponent, maxDivisor); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static HeuristicDivisorPreparation PrepareHeuristicDivisor(in HeuristicDivisorCandidate candidate) @@ -490,7 +481,7 @@ public sealed class HeuristicCombinedPrimeTester ulong divisor = candidate.Value; MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); bool hasCycleHint = TryGetCycleLengthHint(divisor, out ulong cycleLength); - return new HeuristicDivisorPreparation(candidate, divisorData, cycleLength, hasCycleHint); + return new(candidate, divisorData, cycleLength, hasCycleHint); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -588,7 +579,7 @@ public sealed class HeuristicCombinedPrimeTester ulong value = entry; byte ending = (byte)(value % 10UL); ushort residue = (ushort)(value % Wheel210); - candidate = new HeuristicDivisorCandidate(value, group, ending, 0, residue); + candidate = new(value, group, ending, 0, residue); return true; } @@ -691,7 +682,7 @@ public sealed class HeuristicCombinedPrimeTester } ushort residue = (ushort)(value % Wheel210); - return new HeuristicDivisorCandidate(value, group, ending, 0, residue); + return new(value, group, ending, 0, residue); } } } diff --git a/PerfectNumbers.Core/HeuristicPrimeTester.cs b/PerfectNumbers.Core/HeuristicPrimeTester.cs index e9b15bd..c3e0213 100644 --- a/PerfectNumbers.Core/HeuristicPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicPrimeTester.cs @@ -68,7 +68,8 @@ public sealed class HeuristicPrimeTester public static HeuristicPrimeTester Exclusive => _tester ??= new(); - public bool IsPrimeCpu(ulong n) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPrimeCpu(ulong n) { byte nMod10 = (byte)n.Mod10(); ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n); @@ -82,19 +83,18 @@ public sealed class HeuristicPrimeTester return HeuristicTrialDivisionCpu(n, maxDivisorSquare, nMod10, includeGroupB); } - public bool IsPrimeGpu(ulong n) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPrimeGpu(ulong n) { byte nMod10 = (byte)n.Mod10(); ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n); return IsPrimeGpu(n, maxDivisorSquare, nMod10); } - public bool IsPrimeGpu(ulong n, ulong maxDivisorSquare, byte nMod10) - { - return HeuristicIsPrimeGpuCore(n, maxDivisorSquare, nMod10); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPrimeGpu(ulong n, ulong maxDivisorSquare, byte nMod10) => HeuristicIsPrimeGpuCore(n, maxDivisorSquare, nMod10); - private bool HeuristicIsPrimeGpuCore(ulong n, ulong maxDivisorSquare, byte nMod10) + private static bool HeuristicIsPrimeGpuCore(ulong n, ulong maxDivisorSquare, byte nMod10) { // TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner? if (maxDivisorSquare < 9UL) @@ -133,7 +133,7 @@ public sealed class HeuristicPrimeTester break; } - ulong divisor = (ulong)groupADivisors[groupAIndex]; + ulong divisor = groupADivisors[groupAIndex]; if (n % divisor == 0UL) { return false; @@ -186,7 +186,7 @@ public sealed class HeuristicPrimeTester break; } - ulong divisor = (ulong)groupADivisors[groupAIndex]; + ulong divisor = groupADivisors[groupAIndex]; if (n % divisor == 0UL) { return false; @@ -240,7 +240,7 @@ public sealed class HeuristicPrimeTester } - private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) + private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) { // GpuPrimeWorkLimiter.Acquire(); var gpu = PrimeOrderCalculatorAccelerator.Rent(1); @@ -321,17 +321,11 @@ public sealed class HeuristicPrimeTester return compositeDetected; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool EvaluateWithOpenNumericFallback(ulong n) => Prime.Numbers.IsPrime(n); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool EvaluateWithOpenNumericFallback(ulong n) - { - return Prime.Numbers.IsPrime(n); - } - internal static ulong ComputeHeuristicDivisorSquareLimit(ulong n) - { - return n; - } + internal static ulong ComputeHeuristicDivisorSquareLimit(ulong n) => n; private static ReadOnlySpan GetGroupBEndingOrder(byte nMod10) => nMod10 switch { @@ -339,7 +333,7 @@ public sealed class HeuristicPrimeTester 3 => GroupBEndingOrderMod3, 7 => GroupBEndingOrderMod7, 9 => GroupBEndingOrderMod9, - _ => ReadOnlySpan.Empty, + _ => [], }; [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -415,19 +409,19 @@ public sealed class HeuristicPrimeTester private static ReadOnlySpan GetGroupBDivisors(byte ending) => ending switch { 1 => DivisorGenerator.SmallPrimesLastOneWithoutLastThree, - 3 => ReadOnlySpan.Empty, + 3 => [], 7 => DivisorGenerator.SmallPrimesLastSevenWithoutLastThree, 9 => DivisorGenerator.SmallPrimesLastNineWithoutLastThree, - _ => ReadOnlySpan.Empty, + _ => [], }; private static ReadOnlySpan GetGroupBDivisorSquares(byte ending) => ending switch { 1 => DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree, - 3 => ReadOnlySpan.Empty, + 3 => [], 7 => DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree, 9 => DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree, - _ => ReadOnlySpan.Empty, + _ => [], }; private static int InitializeGroupBStates(byte nMod10, Span buffer) @@ -444,16 +438,11 @@ public sealed class HeuristicPrimeTester } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static HeuristicDivisorEnumerator CreateHeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span groupBBuffer) - { - return new HeuristicDivisorEnumerator(maxDivisorSquare, nMod10, groupBBuffer); - } + internal static HeuristicDivisorEnumerator CreateHeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span groupBBuffer) => new(maxDivisorSquare, nMod10, groupBBuffer); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static MersenneHeuristicDivisorEnumerator CreateMersenneDivisorEnumerator(ulong exponent, ulong maxDivisor) - { - return new MersenneHeuristicDivisorEnumerator(exponent, maxDivisor); - } + => new(exponent, maxDivisor); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static HeuristicDivisorPreparation PrepareHeuristicDivisor(in HeuristicDivisorCandidate candidate) @@ -611,25 +600,18 @@ public sealed class HeuristicPrimeTester - internal struct HeuristicGroupBSequenceState + internal struct HeuristicGroupBSequenceState(byte ending, byte priorityIndex) { - public byte Ending; - public byte PriorityIndex; - public int Index; - - public HeuristicGroupBSequenceState(byte ending, byte priorityIndex) - { - Ending = ending; - PriorityIndex = priorityIndex; - Index = 0; - } + public byte Ending = ending; + public byte PriorityIndex = priorityIndex; + public int Index = 0; public bool TryPeek(ulong maxDivisorSquare, out ulong divisor) { ReadOnlySpan divisors = GetGroupBDivisors(Ending); ReadOnlySpan squares = GetGroupBDivisorSquares(Ending); int index = Index; - if ((uint)index >= (uint)divisors.Length) + if (index >= divisors.Length) { divisor = 0UL; return false; @@ -642,7 +624,7 @@ public sealed class HeuristicPrimeTester return false; } - divisor = (ulong)divisors[index]; + divisor = divisors[index]; return true; } @@ -731,11 +713,11 @@ public sealed class HeuristicPrimeTester residueStepper.Advance(); } - public ulong ProcessedCount => processedCount; + public readonly ulong ProcessedCount => processedCount; - public ulong LastDivisor => lastDivisor; + public readonly ulong LastDivisor => lastDivisor; - public bool Exhausted => !active; + public readonly bool Exhausted => !active; private static HeuristicDivisorCandidate CreateCandidate(ulong value) { diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 6310fd1..ec0a3bb 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -265,7 +265,7 @@ public sealed class PrimeTester // ResidueComputationBenchmarks so divisor-cycle metadata can short-circuit the test // instead of recomputing binary GCD for every candidate. ulong m = (ulong)BitOperations.Log2(n); - return BinaryGcd(n, m) != 1UL; + return n.BinaryGcd(m) != 1UL; } internal static void SharesFactorWithMaxExponentBatch(ReadOnlySpan values, Span results) @@ -298,39 +298,4 @@ public sealed class PrimeTester PrimeOrderCalculatorAccelerator.Return(gpu); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong BinaryGcd(ulong u, ulong v) - { - // TODO: Swap this handwritten binary GCD for the optimized helper measured in - // GpuUInt128BinaryGcdBenchmarks so CPU callers share the faster subtract-less - // ladder once the common implementation is promoted into PerfectNumbers.Core. - // You have an example in the benchmarks and you already implemented it in - // GpuUInt128, iirc. - if (u == 0UL) - { - return v; - } - - if (v == 0UL) - { - return u; - } - - int shift = BitOperations.TrailingZeroCount(u | v); - u >>= BitOperations.TrailingZeroCount(u); - - do - { - v >>= BitOperations.TrailingZeroCount(v); - if (u > v) - { - (u, v) = (v, u); - } - - v -= u; - } - while (v != 0UL); - - return u << shift; - } }