From 2b0d7278db3803a4f49349b5b092a5c3ecc7617d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Fri, 10 Oct 2025 10:41:47 +0200 Subject: [PATCH] Replace Pollard-Rho random sources with deterministic generator --- PerfectNumbers.Core/DeterministicRandom.cs | 49 +++++++++++++++++++ PerfectNumbers.Core/MersenneDivisorCycles.cs | 40 +-------------- .../MersennePrimeFactorTester.cs | 15 ++---- PerfectNumbers.Core/PrimeOrderCalculator.cs | 39 ++++++++------- 4 files changed, 76 insertions(+), 67 deletions(-) create mode 100644 PerfectNumbers.Core/DeterministicRandom.cs diff --git a/PerfectNumbers.Core/DeterministicRandom.cs b/PerfectNumbers.Core/DeterministicRandom.cs new file mode 100644 index 0000000..b889d31 --- /dev/null +++ b/PerfectNumbers.Core/DeterministicRandom.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading; + +namespace PerfectNumbers.Core; + +internal static class DeterministicRandom +{ + [ThreadStatic] + private static ulong s_threadState; + + private static long s_seedCounter; + + public static ulong NextUInt64() + { + ulong state = s_threadState; + if (state == 0UL) + { + state = InitializeThreadState(); + } + + state ^= state >> 12; + state ^= state << 25; + state ^= state >> 27; + s_threadState = state; + return state * 2685821657736338717UL; + } + + public static UInt128 NextUInt128() + { + UInt128 high = (UInt128)NextUInt64(); + UInt128 low = (UInt128)NextUInt64(); + return (high << 64) | low; + } + + private static ulong InitializeThreadState() + { + ulong seed = (ulong)Interlocked.Increment(ref s_seedCounter); + seed += 0x9E3779B97F4A7C15UL; + seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL; + seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL; + seed ^= seed >> 31; + if (seed == 0UL) + { + seed = 0x9E3779B97F4A7C15UL; + } + + return seed; + } +} diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 1827fea..d02630b 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Text; -using System.Threading; using PerfectNumbers.Core.Gpu; using ILGPU; using ILGPU.Runtime; @@ -16,11 +15,6 @@ public class MersenneDivisorCycles // Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown private ulong[]? _smallCycles; - [ThreadStatic] - private static ulong _threadRandomState; - - private static long _randomSeedCounter; - public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles(); public MersenneDivisorCycles() @@ -535,8 +529,8 @@ public class MersenneDivisorCycles while (true) { - ulong c = (NextRandomUInt64() % (n - 1UL)) + 1UL; - ulong x = (NextRandomUInt64() % (n - 2UL)) + 2UL; + ulong c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL; + ulong x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL; ulong y = x; ulong d = 1UL; @@ -595,36 +589,6 @@ public class MersenneDivisorCycles } } - private static ulong NextRandomUInt64() - { - ulong state = _threadRandomState; - if (state == 0UL) - { - state = InitializeThreadRandomState(); - } - - state ^= state >> 12; - state ^= state << 25; - state ^= state >> 27; - _threadRandomState = state; - return state * 2685821657736338717UL; - } - - private static ulong InitializeThreadRandomState() - { - ulong seed = (ulong)Interlocked.Increment(ref _randomSeedCounter); - seed += 0x9E3779B97F4A7C15UL; - seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL; - seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL; - seed ^= seed >> 31; - if (seed == 0UL) - { - seed = 0x9E3779B97F4A7C15UL; - } - - return seed; - } - public readonly struct FactorCacheEntry { public FactorCacheEntry(ulong[] primes, byte[] exponents, int count) diff --git a/PerfectNumbers.Core/MersennePrimeFactorTester.cs b/PerfectNumbers.Core/MersennePrimeFactorTester.cs index 1080414..f480e12 100644 --- a/PerfectNumbers.Core/MersennePrimeFactorTester.cs +++ b/PerfectNumbers.Core/MersennePrimeFactorTester.cs @@ -371,10 +371,6 @@ public static class MersennePrimeFactorTester return 2UL; } - var rng = new Random(1234567); // TODO: Replace this RNG with the deterministic span-based - // sequence measured faster in PollardRhoBenchmarks so we avoid - // per-call Random allocations. - ulong c, d, x, y, diff; while (true) { @@ -383,8 +379,8 @@ public static class MersennePrimeFactorTester return 0UL; } - c = (ulong)rng.Next(2, int.MaxValue); - x = (ulong)rng.Next(2, int.MaxValue); + c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL; + x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL; y = x; d = 1UL; @@ -523,9 +519,6 @@ public static class MersennePrimeFactorTester return 2; } - var rng = new Random(1234567); // TODO: Replace with the deterministic UInt128 sequence from - // PollardRho128Benchmarks so we eliminate Random allocations. - UInt128 c, d, x, y, diff; while (true) { @@ -534,8 +527,8 @@ public static class MersennePrimeFactorTester return 0; } - c = (UInt128)rng.NextInt64(2, long.MaxValue); - x = (UInt128)rng.NextInt64(2, long.MaxValue); + c = (DeterministicRandom.NextUInt128() % (n - 1)) + 1; + x = (DeterministicRandom.NextUInt128() % (n - 2)) + 2; y = x; d = 1; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index f96168c..df8e4a2 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Security.Cryptography; using PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core; @@ -1165,7 +1164,11 @@ internal static partial class PrimeOrderCalculator return true; } - Span buffer = stackalloc byte[8]; + ulong c; + ulong x; + ulong y; + ulong d; + ulong diff = 0UL; while (true) { @@ -1174,12 +1177,10 @@ internal static partial class PrimeOrderCalculator return false; } - RandomNumberGenerator.Fill(buffer); - ulong c = (BinaryPrimitives.ReadUInt64LittleEndian(buffer) % (n - 1UL)) + 1UL; - RandomNumberGenerator.Fill(buffer); - ulong x = (BinaryPrimitives.ReadUInt64LittleEndian(buffer) % (n - 2UL)) + 2UL; - ulong y = x; - ulong d = 1UL; + c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL; + x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL; + y = x; + d = 1UL; while (d == 1UL) { @@ -1191,7 +1192,7 @@ internal static partial class PrimeOrderCalculator x = AdvancePolynomial(x, c, n); y = AdvancePolynomial(y, c, n); y = AdvancePolynomial(y, c, n); - ulong diff = x > y ? x - y : y - x; + diff = x > y ? x - y : y - x; d = BinaryGcd(diff, n); } @@ -1396,23 +1397,25 @@ internal static partial class PrimeOrderCalculator return 2UL; } - Span buffer = stackalloc byte[8]; + ulong c; + ulong x; + ulong y; + ulong d; + ulong diff = 0UL; + while (true) { - RandomNumberGenerator.Fill(buffer); - ulong c = (BinaryPrimitives.ReadUInt64LittleEndian(buffer) % (n - 1UL)) + 1UL; - - RandomNumberGenerator.Fill(buffer); - ulong x = (BinaryPrimitives.ReadUInt64LittleEndian(buffer) % (n - 2UL)) + 2UL; - ulong y = x; - ulong d = 1UL; + c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL; + x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL; + y = x; + d = 1UL; while (d == 1UL) { x = AdvancePolynomial(x, c, n); y = AdvancePolynomial(y, c, n); y = AdvancePolynomial(y, c, n); - ulong diff = x > y ? x - y : y - x; + diff = x > y ? x - y : y - x; d = BinaryGcd(diff, n); }