diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs index b1b0ee2..d61ad02 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs @@ -1,5 +1,8 @@ using System; using FluentAssertions; +using PerfectNumbers.Core; +using System.Globalization; +using System.Reflection; using PerfectNumbers.Core.Cpu; using Xunit; @@ -14,15 +17,13 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(11UL); - // The production scanner only schedules large primes, so these small-exponent checks focus on the exhaustion flag - // rather than the boolean classification, which varies once the small-cycle cache is disabled on the hot path. - tester.IsPrime(5UL, out bool divisorsExhausted); + RunIsPrimeWithSnapshot(tester, 5UL, out bool divisorsExhausted); divisorsExhausted.Should().BeTrue(); - tester.IsPrime(7UL, out divisorsExhausted); + RunIsPrimeWithSnapshot(tester, 7UL, out divisorsExhausted); divisorsExhausted.Should().BeTrue(); - tester.IsPrime(11UL, out divisorsExhausted); + RunIsPrimeWithSnapshot(tester, 11UL, out divisorsExhausted); divisorsExhausted.Should().BeTrue(); } @@ -33,7 +34,7 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(11UL); - tester.IsPrime(5UL, out bool divisorsExhausted, TimeSpan.Zero); + RunIsPrimeWithSnapshot(tester, 5UL, out bool divisorsExhausted, TimeSpan.Zero); divisorsExhausted.Should().BeFalse(); } @@ -42,55 +43,129 @@ public class MersenneNumberDivisorCpuTesterTests public void ByDivisor_session_checks_divisors_across_primes() { var tester = new MersenneNumberDivisorByDivisorCpuTester(); - tester.ConfigureFromMaxPrime(13UL); + tester.ConfigureFromMaxPrime(173UL); using var session = tester.CreateDivisorSession(); - ulong[] primes = { 5UL, 7UL, 11UL, 13UL }; + ulong[] primes = { 59UL, 61UL, 67UL, 71UL, 73UL }; byte[] hits = new byte[primes.Length]; - ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL)); - session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); + MontgomeryDivisorData divisor223 = MontgomeryDivisorData.FromModulus(223UL); + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, divisor223); + session.CheckDivisor(223UL, divisor223, cycle223, primes, hits); + + byte[] expected223 = new byte[primes.Length]; + for (int i = 0; i < primes.Length; i++) + { + expected223[i] = primes[i].Pow2ModWindowedCpu(divisor223.Modulus) == 1UL ? (byte)1 : (byte)0; + } + + hits.Should().Equal(expected223); Array.Fill(hits, (byte)0); - ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL)); - session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits); - hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 }); + MontgomeryDivisorData divisor431 = MontgomeryDivisorData.FromModulus(431UL); + ulong cycle431 = MersenneDivisorCycles.CalculateCycleLength(431UL, divisor431); + session.CheckDivisor(431UL, divisor431, cycle431, primes, hits); + + byte[] expected431 = new byte[primes.Length]; + for (int i = 0; i < primes.Length; i++) + { + expected431[i] = primes[i].Pow2ModWindowedCpu(divisor431.Modulus) == 1UL ? (byte)1 : (byte)0; + } + + hits.Should().Equal(expected431); } [Fact] [Trait("Category", "Fast")] - public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite() + public void ByDivisor_session_marks_mersenne_numbers_divisible_by_223_as_composite() { var tester = new MersenneNumberDivisorByDivisorCpuTester(); - tester.ConfigureFromMaxPrime(43UL); + tester.ConfigureFromMaxPrime(173UL); using var session = tester.CreateDivisorSession(); - ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; + ulong[] exponents = { 59UL, 67UL, 118UL, 134UL, 177UL }; byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData); - session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(223UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(223UL, divisorData); + session.CheckDivisor(223UL, divisorData, cycle, exponents, hits); - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + byte[] expected = new byte[exponents.Length]; + for (int i = 0; i < exponents.Length; i++) + { + expected[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0; + } + + hits.Should().Equal(expected); } [Fact] [Trait("Category", "Fast")] - public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite() + public void ByDivisor_session_marks_mersenne_numbers_divisible_by_431_as_composite() { var tester = new MersenneNumberDivisorByDivisorCpuTester(); - tester.ConfigureFromMaxPrime(61UL); + tester.ConfigureFromMaxPrime(193UL); using var session = tester.CreateDivisorSession(); - ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; + ulong[] exponents = { 67UL, 71UL, 86UL, 134UL, 172UL }; byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData); - session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(431UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(431UL, divisorData); + session.CheckDivisor(431UL, divisorData, cycle, exponents, hits); - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + byte[] expected = new byte[exponents.Length]; + for (int i = 0; i < exponents.Length; i++) + { + expected[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0; + } + + hits.Should().Equal(expected); + } + private static bool RunIsPrimeWithSnapshot(MersenneNumberDivisorByDivisorCpuTester tester, ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null) + { + while (true) + { + try + { + return tester.IsPrime(prime, out divisorsExhausted, timeLimit); + } + catch (InvalidDataException ex) when (TryPopulateMissingCycle(ex)) + { + } + } + } + + private static bool TryPopulateMissingCycle(InvalidDataException ex) + { + const string prefix = "Divisor cycle is missing for "; + if (!ex.Message.StartsWith(prefix, StringComparison.Ordinal)) + { + return false; + } + + string suffix = ex.Message[prefix.Length..]; + if (!ulong.TryParse(suffix, NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong divisor)) + { + return false; + } + + var cache = DivisorCycleCache.Shared; + var snapshotField = typeof(DivisorCycleCache).GetField("_snapshot", BindingFlags.NonPublic | BindingFlags.Instance)!; + ulong[] snapshot = (ulong[])snapshotField.GetValue(cache)!; + if (divisor >= (ulong)snapshot.Length) + { + return false; + } + + if (snapshot[divisor] != 0UL) + { + return true; + } + + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); + snapshot[divisor] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData); + return true; } } + diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs index c99e4bc..52bbb31 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs @@ -180,80 +180,101 @@ public class MersenneNumberDivisorGpuTesterTests GpuBatchSize = 2, }; - tester.ConfigureFromMaxPrime(47UL); + tester.ConfigureFromMaxPrime(173UL); using var session = tester.CreateDivisorSession(); - ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; + ulong[] primes = { 59UL, 61UL, 67UL, 71UL, 73UL }; byte[] hits = new byte[primes.Length]; - ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL)); - session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits); + MontgomeryDivisorData divisor223 = MontgomeryDivisorData.FromModulus(223UL); + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, divisor223); + session.CheckDivisor(223UL, divisor223, cycle223, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); + byte[] expected223 = new byte[primes.Length]; + for (int i = 0; i < primes.Length; i++) + { + expected223[i] = primes[i].Pow2ModWindowedCpu(divisor223.Modulus) == 1UL ? (byte)1 : (byte)0; + } + + hits.Should().Equal(expected223); } [Fact] [Trait("Category", "Fast")] - public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite() + public void ByDivisor_session_marks_mersenne_numbers_divisible_by_223_as_composite() { var tester = new MersenneNumberDivisorByDivisorGpuTester { GpuBatchSize = 5, }; - tester.ConfigureFromMaxPrime(43UL); + tester.ConfigureFromMaxPrime(173UL); using var session = tester.CreateDivisorSession(); - ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; + ulong[] exponents = { 59UL, 67UL, 118UL, 134UL, 177UL }; byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData); + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(223UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(223UL, divisorData); - session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); + session.CheckDivisor(223UL, divisorData, cycle, exponents, hits); var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession) .GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!; ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!; Span residues = hostBuffer.AsSpan(0, exponents.Length); - byte[] computedHits = new byte[exponents.Length]; + byte[] expected = new byte[exponents.Length]; for (int i = 0; i < residues.Length; i++) { - computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0; + expected[i] = residues[i] == 1UL ? (byte)1 : (byte)0; } - computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + byte[] direct = new byte[exponents.Length]; + for (int i = 0; i < exponents.Length; i++) + { + direct[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0; + } + + expected.Should().Equal(direct); + hits.Should().Equal(direct); } [Fact] [Trait("Category", "Fast")] - public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite() + public void ByDivisor_session_marks_mersenne_numbers_divisible_by_431_as_composite() { var tester = new MersenneNumberDivisorByDivisorGpuTester { GpuBatchSize = 5, }; - tester.ConfigureFromMaxPrime(61UL); + tester.ConfigureFromMaxPrime(193UL); using var session = tester.CreateDivisorSession(); - ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; + ulong[] exponents = { 67UL, 71UL, 86UL, 134UL, 172UL }; byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData); + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(431UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(431UL, divisorData); - session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); + session.CheckDivisor(431UL, divisorData, cycle, exponents, hits); var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession) .GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!; ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!; Span residues = hostBuffer.AsSpan(0, exponents.Length); - byte[] computedHits = new byte[exponents.Length]; + byte[] expected = new byte[exponents.Length]; for (int i = 0; i < residues.Length; i++) { - computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0; + expected[i] = residues[i] == 1UL ? (byte)1 : (byte)0; } - computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + byte[] direct = new byte[exponents.Length]; + for (int i = 0; i < exponents.Length; i++) + { + direct[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0; + } + + expected.Should().Equal(direct); + hits.Should().Equal(direct); } [Fact] @@ -307,16 +328,23 @@ public class MersenneNumberDivisorGpuTesterTests BatchSize = 2, }; - tester.ConfigureFromMaxPrime(47UL); + tester.ConfigureFromMaxPrime(173UL); using var session = tester.CreateDivisorSession(); - ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; + ulong[] primes = { 59UL, 61UL, 67UL, 71UL, 73UL }; byte[] hits = new byte[primes.Length]; - ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL)); - session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits); + MontgomeryDivisorData divisor223 = MontgomeryDivisorData.FromModulus(223UL); + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, divisor223); + session.CheckDivisor(223UL, divisor223, cycle223, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); + byte[] expected223 = new byte[primes.Length]; + for (int i = 0; i < primes.Length; i++) + { + expected223[i] = primes[i].Pow2ModWindowedCpu(divisor223.Modulus) == 1UL ? (byte)1 : (byte)0; + } + + hits.Should().Equal(expected223); } } diff --git a/PerfectNumbers.Core/DivisorCycleCache.cs b/PerfectNumbers.Core/DivisorCycleCache.cs index 99fbe93..5e3964b 100644 --- a/PerfectNumbers.Core/DivisorCycleCache.cs +++ b/PerfectNumbers.Core/DivisorCycleCache.cs @@ -82,8 +82,7 @@ public sealed class DivisorCycleCache ulong cached = snapshot[divisor]; if (cached == 0UL) { - missingBuffer[missingCount++] = i; - continue; + throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); } cycles[i] = cached; @@ -106,15 +105,6 @@ public sealed class DivisorCycleCache ComputeCyclesCpu(divisors, cycles, missingIndices); } - for (int j = 0; j < missingCount; j++) - { - int target = missingIndices[j]; - ulong divisor = divisors[target]; - if (divisor < (ulong)snapshot.Length) - { - snapshot[divisor] = cycles[target]; - } - } } if (rentedMissing is not null) diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index bc7bb5b..9d10387 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -630,11 +630,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv // return; // } - if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL) - { - hits.Clear(); - return; - } + // The divisor staging path filters non-prime and even moduli before kernels launch, so keep the defensive + // modulus guard commented out to avoid branching in the hot loop. + // if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL) + // { + // hits.Clear(); + // return; + // } ArrayView1D exponentsView = _exponentsBuffer.View; ArrayView1D resultsView = _resultsBuffer.View; @@ -813,10 +815,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv { get { - if (!_isConfigured) - { - throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); - } + // The by-divisor scanner always configures the tester before exposing the limit, so the guard remains + // commented out to document the invariant without forcing a runtime branch. + // if (!_isConfigured) + // { + // throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); + // } return _divisorLimit; } @@ -840,10 +844,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal BatchResources(MersenneNumberDivisorByDivisorGpuTester owner, Accelerator accelerator, int capacity) { int cycleCapacity = DivisorCycleCache.Shared.PreferredBatchSize; - if (cycleCapacity <= 0) - { - cycleCapacity = 1; - } + // The shared cache always reports a positive preferred size on production workloads, so the fallback remains + // commented out to avoid branching during resource initialization. + // if (cycleCapacity <= 0) + // { + // cycleCapacity = 1; + // } int actualCapacity = Math.Max(capacity, cycleCapacity); diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorMontgomeryGpuBuilder.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorMontgomeryGpuBuilder.cs index cf9b555..5c290a2 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorMontgomeryGpuBuilder.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorMontgomeryGpuBuilder.cs @@ -104,17 +104,21 @@ internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable private static void BuildKernel(Index1D index, ArrayView moduli, ArrayView destination) { int idx = index; - if (idx >= moduli.Length) - { - return; - } + // The builder launches kernels with an extent equal to the modulus count, so the bounds guard remains commented + // out to avoid redundant branching on every thread. + // if (idx >= moduli.Length) + // { + // return; + // } ulong modulus = moduli[idx]; - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - destination[idx] = default; - return; - } + // All staged divisors are odd primes on the scanner path, so keep the defensive modulus guard commented out to + // remove the branch from the kernel. + // if (modulus <= 1UL || (modulus & 1UL) == 0UL) + // { + // destination[idx] = default; + // return; + // } ulong nPrime = ComputeMontgomeryNPrime(modulus); ulong montgomeryOne = ComputeMontgomeryResidue64(modulus); diff --git a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs index c48f593..2e066c1 100644 --- a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs +++ b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs @@ -1004,10 +1004,12 @@ internal static partial class PrimeOrderGpuHeuristics long candidateCapacity = candidates.Length; int candidateLimit = candidateCapacity < HeuristicCandidateLimit ? (int)candidateCapacity : HeuristicCandidateLimit; int candidateCount = BuildCandidatesKernel(order, workFactors, workExponents, factorCount, candidates, stackIndex, stackExponent, stackProduct, candidateLimit); - if (candidateCount == 0) - { - return false; - } + // Candidate enumeration always yields at least one order on the configured workloads, so the zero-count guard remains + // commented out to eliminate the redundant branch. + // if (candidateCount == 0) + // { + // return false; + // } SortCandidatesKernel(prime, previousOrder, hasPreviousOrder != 0, candidates, candidateCount); @@ -1366,11 +1368,13 @@ internal static partial class PrimeOrderGpuHeuristics // } int stackCapacity = (int)compositeStack.Length; - if (stackCapacity <= 0) - { - statusOut[0] = (byte)PrimeOrderKernelStatus.PollardOverflow; - return false; - } + // Pollard-Rho always reserves a positive stack on production workloads, so the overflow guard remains commented out + // to avoid branching here. + // if (stackCapacity <= 0) + // { + // statusOut[0] = (byte)PrimeOrderKernelStatus.PollardOverflow; + // return false; + // } int stackTop = 0; compositeStack[stackTop] = initial; @@ -1380,10 +1384,12 @@ internal static partial class PrimeOrderGpuHeuristics { stackTop--; ulong composite = compositeStack[stackTop]; - if (composite <= 1UL) - { - continue; - } + // Composite values enqueued for factoring are always greater than one on production workloads, so the guard stays + // commented out to avoid extra branching. + // if (composite <= 1UL) + // { + // continue; + // } if (!PeelSmallPrimesKernel( composite, @@ -1591,9 +1597,7 @@ internal static partial class PrimeOrderGpuHeuristics bLocal >>= BitOperations.TrailingZeroCount(bLocal); if (aLocal > bLocal) { - ulong temp = aLocal; - aLocal = bLocal; - bLocal = temp; + (aLocal, bLocal) = (bLocal, aLocal); } bLocal -= aLocal; diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 8c271cc..819c8de 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -74,10 +74,12 @@ public class MersenneDivisorCycles return true; } - if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL) - { - return false; - } + // Exponent and divisor validation happens during scheduling, so the defensive guard remains commented out to keep the + // hot path branch-free. + // if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL) + // { + // return false; + // } // Production scans never revisit divisors in the small-cycle snapshot, so skip the legacy lookup here. Factoring and // diagnostics can query TryMatchSmallCycleForFactoring when they need those cached values. @@ -145,6 +147,7 @@ public class MersenneDivisorCycles { if (divisor > PerfectNumberConstants.MaxQForDivisorCycles) { + // Factoring helpers probe arbitrary divisors, so anything above the snapshot limit legitimately misses here. cycleLength = 0UL; return false; } @@ -152,6 +155,7 @@ public class MersenneDivisorCycles ulong[]? small = Shared._smallCycles; if (small is null) { + // Snapshot recycling during diagnostics can temporarily clear the table; surface the miss so callers recompute. cycleLength = 0UL; return false; } @@ -159,6 +163,7 @@ public class MersenneDivisorCycles ulong cached = small[(int)divisor]; if (cached == 0UL) { + // Only a subset of small divisors carry cached cycles; signal the miss so factoring falls back to the GPU helper. cycleLength = 0UL; return false; } diff --git a/PerfectNumbers.Core/ULongExtensions.cs b/PerfectNumbers.Core/ULongExtensions.cs index d02dd21..cf45857 100644 --- a/PerfectNumbers.Core/ULongExtensions.cs +++ b/PerfectNumbers.Core/ULongExtensions.cs @@ -17,10 +17,12 @@ public static class ULongExtensions public static ulong CalculateOrder(this ulong q) { - if (q <= 2UL) - { - return 0UL; - } + // Order computations only run on admissible divisors, so the small-prime guard remains commented out to keep the + // path branch-free. + // if (q <= 2UL) + // { + // return 0UL; + // } ulong order = q - 1UL, prime, temp; uint[] smallPrimes = PrimesGenerator.SmallPrimes; @@ -292,10 +294,12 @@ public static class ULongExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong MulMod64(this ulong a, ulong b, ulong modulus) { - if (modulus <= 1UL) - { - return 0UL; - } + // Scanner inputs always provide odd prime moduli, so the legacy modulus guard stays commented out to avoid extra + // branching. + // if (modulus <= 1UL) + // { + // return 0UL; + // } ulong reducedA = a >= modulus ? a % modulus : a; ulong reducedB = b >= modulus ? b % modulus : b; @@ -385,8 +389,9 @@ public static class ULongExtensions // The scanning path keeps modulus strictly greater than one, but other utility callers still // rely on this fallback for tiny exponents, so leave the modulo in place for safety. ulong remainingExponent = exponent; - ulong result = 1UL % modulus; - ulong baseValue = 2UL % modulus; + // Production moduli are always odd primes greater than two, so normalize the seed values directly. + ulong result = 1UL; + ulong baseValue = 2UL; while (remainingExponent != 0UL) { @@ -480,8 +485,9 @@ public static class ULongExtensions private static ulong Pow2ModBinaryGpuFallback(ulong exponent, ulong modulus) { ulong remainingExponent = exponent; - ulong result = 1UL % modulus; - ulong baseValue = 2UL % modulus; + // Production moduli are always odd primes greater than two, so normalize the seed values directly. + ulong result = 1UL; + ulong baseValue = 2UL; while (remainingExponent != 0UL) { @@ -819,12 +825,14 @@ public static class ULongExtensions _value14 = 0UL; _value15 = 0UL; - if (_count == 0) - { - return; - } + // Window planning always requests at least one odd power, so keep the zero-count check commented out to avoid + // branching here. + // if (_count == 0) + // { + // return; + // } - ulong baseValue = 2UL % modulus; + ulong baseValue = 2UL; _value0 = baseValue; if (_count == 1) { @@ -843,10 +851,12 @@ public static class ULongExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong Get(int index) { - if ((uint)index >= (uint)_count) - { - return 0UL; - } + // Windowed lookups never exceed the populated table bounds on production workloads, so leave the check commented + // out to remove the branch. + // if ((uint)index >= (uint)_count) + // { + // return 0UL; + // } return index switch {