From b9ca95bf8c72858f32ad1a1d65fa7e8f0e3e86e6 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 24 Nov 2025 22:51:53 +0100 Subject: [PATCH] PrimeOrderCalculatorAccelerator.Rent is now created outside of the loops and re-used for all calculations. --- .../Pow2ModBenchmarks.cs | 1969 +++++++++-------- .../Pow2MontgomeryModBenchmarks.cs | 796 +++---- .../Pow2MontgomeryModWindowedBenchmarkData.cs | 5 +- ...yModWindowedKeepMontgomeryGpuBenchmarks.cs | 9 +- .../PrimeTesterBenchmarks.cs | 18 +- EvenPerfectBitScanner.Tests/ProgramTests.cs | 57 +- .../Candidates/CandidatesCalculator.cs | 5 +- EvenPerfectBitScanner/Program.cs | 23 +- .../Gpu/LucasLehmerPrimeTesterTests.cs | 133 +- .../Gpu/PrimeOrderGpuHeuristicsTests.cs | 645 +++--- .../Gpu/PrimeTesterGpuTests.cs | 44 +- .../MersenneDivisorCyclesGenerateGpuTests.cs | 181 +- .../MersenneDivisorCyclesTests.cs | 41 +- ...rsenneNumberDivisorByDivisorTesterTests.cs | 5 +- .../MersenneNumberDivisorCpuTesterTests.cs | 179 +- .../MersenneNumberDivisorGpuTesterTests.cs | 670 +++--- ...MersenneNumberIncrementalCpuTesterTests.cs | 53 +- .../MersenneNumberOrderCpuTesterTests.cs | 53 +- .../MersenneNumberResidueCpuTesterTests.cs | 43 +- .../MersenneNumberTesterTests.cs | 71 +- .../MersenneResidueModeTests.cs | 59 +- .../ModResidueTrackerTests.cs | 127 +- .../PrimeOrderCalculatorTests.cs | 362 +-- .../PrimeTester/PrimeTesterGcdTests.cs | 39 +- .../PrimeTester/PrimeTesterIsPrimeTests.cs | 139 +- .../UInt128ExtensionsTests.cs | 471 ++-- .../ULongExtensionsTests.cs | 391 ++-- .../Cpu/MersenneCpuDivisorScanSession.cs | 9 +- ...MersenneNumberDivisorByDivisorCpuTester.cs | 25 +- .../Cpu/MersenneNumberIncrementalCpuTester.cs | 158 +- .../Cpu/MersenneNumberLucasLehmerCpuTester.cs | 72 +- .../Cpu/MersenneNumberOrderCpuTester.cs | 68 +- .../Cpu/MersenneNumberResidueCpuTester.cs | 12 +- PerfectNumbers.Core/DivisorCycleCache.cs | 14 +- ...MersenneNumberDivisorByDivisorGpuTester.cs | 91 +- .../Gpu/MersenneNumberDivisorGpuTester.cs | 5 +- .../Gpu/MersenneNumberLucasLehmerGpuTester.cs | 5 +- .../Gpu/Pow2MontgomeryGpuCalculator.cs | 34 +- .../Gpu/PrimeOrderGpuHeuristics.cs | 28 +- .../HeuristicCombinedPrimeTester.cs | 20 +- PerfectNumbers.Core/HeuristicPrimeTester.cs | 16 +- .../IMersenneNumberDivisorByDivisorTester.cs | 6 +- PerfectNumbers.Core/MersenneDivisorCycles.cs | 23 +- .../MersenneNumberDivisorByDivisorTester.cs | 13 +- PerfectNumbers.Core/MersenneNumberTester.cs | 23 +- PerfectNumbers.Core/ModResidueTracker.cs | 16 +- .../PrimeOrderCalculator.Cpu.cs | 6 +- .../PrimeOrderCalculator.Shared.cs | 61 +- PerfectNumbers.Core/PrimeOrderCalculator.cs | 9 +- PerfectNumbers.Core/PrimeTester.cs | 72 +- PerfectNumbers.Core/UInt128Extensions.cs | 1463 ++++++------ PerfectNumbers.Core/ULongExtensions.Gpu.cs | 16 +- PerfectNumbers.Core/ULongExtensions.cs | 15 +- 53 files changed, 4639 insertions(+), 4229 deletions(-) diff --git a/EvenPerfectBitScanner.Benchmarks/Pow2ModBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Pow2ModBenchmarks.cs index ea0a4c7..6245095 100644 --- a/EvenPerfectBitScanner.Benchmarks/Pow2ModBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Pow2ModBenchmarks.cs @@ -4,6 +4,7 @@ using BenchmarkDotNet.Jobs; using System.Numerics; using PerfectNumbers.Core; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; namespace EvenPerfectBitScanner.Benchmarks; @@ -22,182 +23,190 @@ namespace EvenPerfectBitScanner.Benchmarks; [SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)] public class Pow2ModBenchmarks { - private const int SampleCount = 256; - private const ulong OneShiftedLeft63 = 1UL << 63; - private const long OneShiftedLeft60 = 1L << 60; - private const ulong Pow2WindowFallbackThreshold = 32UL; + private const int SampleCount = 256; + private const ulong OneShiftedLeft63 = 1UL << 63; + private const long OneShiftedLeft60 = 1L << 60; + private const ulong Pow2WindowFallbackThreshold = 32UL; - private MontgomeryDivisorData[] _smallDivisors = Array.Empty(); - private MontgomeryDivisorData[] _largeDivisors = Array.Empty(); - private MontgomeryDivisorData[] _veryLargeDivisors = Array.Empty(); - private UInt128[] _wideModuli = Array.Empty(); - private UInt128[] _wideExponents = Array.Empty(); - private ulong[] _smallExponents = Array.Empty(); - private ulong[] _largeExponents = Array.Empty(); - private ulong[] _veryLargeExponents = Array.Empty(); - private ulong[] _smallCycles = Array.Empty(); - private ulong[] _largeCycles = Array.Empty(); - private ulong[] _veryLargeCycles = Array.Empty(); - private UInt128[] _wideCycles = Array.Empty(); - private ulong? _previousPrimeOrder; - private UInt128? _previousWidePrimeOrder; + private MontgomeryDivisorData[] _smallDivisors = Array.Empty(); + private MontgomeryDivisorData[] _largeDivisors = Array.Empty(); + private MontgomeryDivisorData[] _veryLargeDivisors = Array.Empty(); + private UInt128[] _wideModuli = Array.Empty(); + private UInt128[] _wideExponents = Array.Empty(); + private ulong[] _smallExponents = Array.Empty(); + private ulong[] _largeExponents = Array.Empty(); + private ulong[] _veryLargeExponents = Array.Empty(); + private ulong[] _smallCycles = Array.Empty(); + private ulong[] _largeCycles = Array.Empty(); + private ulong[] _veryLargeCycles = Array.Empty(); + private UInt128[] _wideCycles = Array.Empty(); + private ulong? _previousPrimeOrder; + private UInt128? _previousWidePrimeOrder; - public enum InputScale - { - Small, - Large, - VeryLarge - } + public enum InputScale + { + Small, + Large, + VeryLarge + } - [ParamsAllValues] - public InputScale Scale { get; set; } + [ParamsAllValues] + public InputScale Scale { get; set; } - [GlobalSetup] - public void Setup() - { - SampleCollections cache = SampleCollectionsProvider.Instance; - _smallDivisors = cache.SmallDivisors; - _largeDivisors = cache.LargeDivisors; - _veryLargeDivisors = cache.VeryLargeDivisors; - _wideModuli = cache.WideModuli; - _wideExponents = cache.WideExponents; - _smallExponents = cache.SmallExponents; - _largeExponents = cache.LargeExponents; - _veryLargeExponents = cache.VeryLargeExponents; - _smallCycles = cache.SmallCycles; - _largeCycles = cache.LargeCycles; - _veryLargeCycles = cache.VeryLargeCycles; - _wideCycles = cache.WideCycles; - } + [GlobalSetup] + public void Setup() + { + SampleCollections cache = SampleCollectionsProvider.Instance; + _smallDivisors = cache.SmallDivisors; + _largeDivisors = cache.LargeDivisors; + _veryLargeDivisors = cache.VeryLargeDivisors; + _wideModuli = cache.WideModuli; + _wideExponents = cache.WideExponents; + _smallExponents = cache.SmallExponents; + _largeExponents = cache.LargeExponents; + _veryLargeExponents = cache.VeryLargeExponents; + _smallCycles = cache.SmallCycles; + _largeCycles = cache.LargeCycles; + _veryLargeCycles = cache.VeryLargeCycles; + _wideCycles = cache.WideCycles; + } - /// - /// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one. - /// - [Benchmark] - public ulong MontgomeryWindowedCpu() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one. + /// + [Benchmark] + public ulong MontgomeryWindowedCpu() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false); + } - return checksum; - } + return checksum; + } - /// - /// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one. + /// + /// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one. /// /// GPU benchmark failed running, hanging up early before reaching workload testing. - /// - // [Benchmark] - public ulong MontgomeryWindowedGpu() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + // [Benchmark] + public ulong MontgomeryWindowedGpu() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(gpu, divisors[i]); + } - return checksum; - } + PrimeOrderCalculatorAccelerator.Return(gpu); + return checksum; + } - /// - /// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline). - /// - [Benchmark] - public ulong MontgomeryWindowedWithCycleCpu() - { - GetData(out var exponents, out var divisors, out var cycles); - ulong checksum = 0UL; + /// + /// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline). + /// + [Benchmark] + public ulong MontgomeryWindowedWithCycleCpu() + { + GetData(out var exponents, out var divisors, out var cycles); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]); + } - return checksum; - } + return checksum; + } - /// - /// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline). + /// + /// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline). /// /// GPU benchmark failed running, hanging up early before reaching workload testing. - /// - // [Benchmark] - public ulong MontgomeryWindowedWithCycleGpu() - { - GetData(out var exponents, out var divisors, out var cycles); - ulong checksum = 0UL; + /// + // [Benchmark] + public ulong MontgomeryWindowedWithCycleGpu() + { + GetData(out var exponents, out var divisors, out var cycles); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycles[i], divisors[i]); + } - return checksum; - } + PrimeOrderCalculatorAccelerator.Return(gpu); + return checksum; + } - /// - /// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to - /// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline). + /// + /// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to + /// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline). /// /// GPU benchmark failed running, hanging up early before reaching workload testing. - /// - // [Benchmark] - public ulong MontgomeryWithGpuCycleComputationCpu() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + // [Benchmark] + public ulong MontgomeryWithGpuCycleComputationCpu() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus); - checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus); + checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]); + } - return checksum; - } + return checksum; + } - /// - /// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to - /// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline). + /// + /// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to + /// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline). /// /// GPU benchmark failed running, hanging up early before reaching workload testing. - /// - // [Benchmark] - public ulong MontgomeryWithGpuCycleComputationGpu() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + // [Benchmark] + public ulong MontgomeryWithGpuCycleComputationGpu() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus); - checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus); + checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycle, divisors[i]); + } - return checksum; - } + PrimeOrderCalculatorAccelerator.Return(gpu); + return checksum; + } - [Benchmark] - public ulong MontgomeryWithHeuristicCycleComputationCpu() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + [Benchmark] + public ulong MontgomeryWithHeuristicCycleComputationCpu() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus); - checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + ulong cycle = CalculateCycleLengthWithHeuristics(gpu, divisors[i].Modulus); + checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]); + } - return checksum; - } + PrimeOrderCalculatorAccelerator.Return(gpu); + return checksum; + } /// /// GPU benchmark failed running, hanging up early before reaching workload testing. @@ -209,918 +218,928 @@ public class Pow2ModBenchmarks GetData(out var exponents, out var divisors, out _); ulong checksum = 0UL; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (int i = 0; i < SampleCount; i++) { - ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus); - checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]); + ulong cycle = CalculateCycleLengthWithHeuristics(gpu, divisors[i].Modulus); + checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycle, divisors[i]); + } + + PrimeOrderCalculatorAccelerator.Return(gpu); + return checksum; + } + + [Benchmark] + public UInt128 HeuristicWideCycleComputation() + { + UInt128 checksum = UInt128.Zero; + + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + UInt128 cycle = CalculateCycleLengthWithHeuristics(gpu, _wideModuli[i]); + checksum ^= cycle; + } + + PrimeOrderCalculatorAccelerator.Return(gpu); + return checksum; + } + + /// + /// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×). + /// + [Benchmark] + public ulong LeftToRight() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; + + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]); } return checksum; } - [Benchmark] - public UInt128 HeuristicWideCycleComputation() - { - UInt128 checksum = UInt128.Zero; + /// + /// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×). + /// + [Benchmark] + public ulong Batched4() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - UInt128 cycle = CalculateCycleLengthWithHeuristics(_wideModuli[i]); - checksum ^= cycle; - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]); + } - return checksum; - } + return checksum; + } - /// - /// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×). - /// - [Benchmark] - public ulong LeftToRight() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×). + /// + [Benchmark] + public ulong SlidingWindow() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]); + } - return checksum; - } + return checksum; + } - /// - /// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×). - /// - [Benchmark] - public ulong Batched4() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×). + /// + [Benchmark(Baseline = true)] + public ulong StandardPow2Mod() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus); + } - return checksum; - } + return checksum; + } - /// - /// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×). - /// - [Benchmark] - public ulong SlidingWindow() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×). + /// + [Benchmark] + public ulong PrecomputedTableMod() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus); + } - return checksum; - } + return checksum; + } - /// - /// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×). - /// - [Benchmark(Baseline = true)] - public ulong StandardPow2Mod() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Relies on ; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×). + /// + [Benchmark] + public ulong BigIntegerMod() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus); + } - return checksum; - } + return checksum; + } - /// - /// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×). - /// - [Benchmark] - public ulong PrecomputedTableMod() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Current UInt128-native windowed Montgomery ladder that avoids GpuUInt128 on CPU paths. + /// + [Benchmark] + public UInt128 MontgomeryWindowedUInt128() + { + UInt128 checksum = UInt128.Zero; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= _wideExponents[i].Pow2MontgomeryModWindowed(_wideModuli[i]); + } - return checksum; - } + return checksum; + } - /// - /// Relies on ; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×). - /// - [Benchmark] - public ulong BigIntegerMod() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Previous CPU shim that reused the GPU-oriented GpuUInt128 implementation. + /// + [Benchmark] + public UInt128 MontgomeryWindowedGpuShimUInt128() + { + UInt128 checksum = UInt128.Zero; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2MontgomeryModWindowedGpuShim(_wideExponents[i], _wideModuli[i]); + } - return checksum; - } + return checksum; + } - /// - /// Current UInt128-native windowed Montgomery ladder that avoids GpuUInt128 on CPU paths. - /// - [Benchmark] - public UInt128 MontgomeryWindowedUInt128() - { - UInt128 checksum = UInt128.Zero; + private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles) + { + switch (Scale) + { + case InputScale.Small: + exponents = _smallExponents; + divisors = _smallDivisors; + cycles = _smallCycles; + break; + case InputScale.Large: + exponents = _largeExponents; + divisors = _largeDivisors; + cycles = _largeCycles; + break; + case InputScale.VeryLarge: + exponents = _veryLargeExponents; + divisors = _veryLargeDivisors; + cycles = _veryLargeCycles; + break; + default: + throw new ArgumentOutOfRangeException(nameof(Scale), Scale, null); + } + } - for (int i = 0; i < SampleCount; i++) - { - checksum ^= _wideExponents[i].Pow2MontgomeryModWindowed(_wideModuli[i]); - } + private sealed class SampleCollections + { + public SampleCollections( + MontgomeryDivisorData[] smallDivisors, + MontgomeryDivisorData[] largeDivisors, + MontgomeryDivisorData[] veryLargeDivisors, + UInt128[] wideModuli, + UInt128[] wideExponents, + ulong[] smallExponents, + ulong[] largeExponents, + ulong[] veryLargeExponents, + ulong[] smallCycles, + ulong[] largeCycles, + ulong[] veryLargeCycles, + UInt128[] wideCycles) + { + SmallDivisors = smallDivisors; + LargeDivisors = largeDivisors; + VeryLargeDivisors = veryLargeDivisors; + WideModuli = wideModuli; + WideExponents = wideExponents; + SmallExponents = smallExponents; + LargeExponents = largeExponents; + VeryLargeExponents = veryLargeExponents; + SmallCycles = smallCycles; + LargeCycles = largeCycles; + VeryLargeCycles = veryLargeCycles; + WideCycles = wideCycles; + } - return checksum; - } + public MontgomeryDivisorData[] SmallDivisors { get; } + public MontgomeryDivisorData[] LargeDivisors { get; } + public MontgomeryDivisorData[] VeryLargeDivisors { get; } + public UInt128[] WideModuli { get; } + public UInt128[] WideExponents { get; } + public ulong[] SmallExponents { get; } + public ulong[] LargeExponents { get; } + public ulong[] VeryLargeExponents { get; } + public ulong[] SmallCycles { get; } + public ulong[] LargeCycles { get; } + public ulong[] VeryLargeCycles { get; } + public UInt128[] WideCycles { get; } + } - /// - /// Previous CPU shim that reused the GPU-oriented GpuUInt128 implementation. - /// - [Benchmark] - public UInt128 MontgomeryWindowedGpuShimUInt128() - { - UInt128 checksum = UInt128.Zero; + private static class SampleCollectionsProvider + { + private static readonly Lazy Cache = new(Create); - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2MontgomeryModWindowedGpuShim(_wideExponents[i], _wideModuli[i]); - } + public static SampleCollections Instance => Cache.Value; - return checksum; - } + private static SampleCollections Create() + { + Console.WriteLine("Preparing power-of-two modulus benchmark sample collections..."); - private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles) - { - switch (Scale) - { - case InputScale.Small: - exponents = _smallExponents; - divisors = _smallDivisors; - cycles = _smallCycles; - break; - case InputScale.Large: - exponents = _largeExponents; - divisors = _largeDivisors; - cycles = _largeCycles; - break; - case InputScale.VeryLarge: - exponents = _veryLargeExponents; - divisors = _veryLargeDivisors; - cycles = _veryLargeCycles; - break; - default: - throw new ArgumentOutOfRangeException(nameof(Scale), Scale, null); - } - } + Random random = new(13); + Random heuristicRandom = new(113); + Random wideRandom = new(19); - private sealed class SampleCollections - { - public SampleCollections( - MontgomeryDivisorData[] smallDivisors, - MontgomeryDivisorData[] largeDivisors, - MontgomeryDivisorData[] veryLargeDivisors, - UInt128[] wideModuli, - UInt128[] wideExponents, - ulong[] smallExponents, - ulong[] largeExponents, - ulong[] veryLargeExponents, - ulong[] smallCycles, - ulong[] largeCycles, - ulong[] veryLargeCycles, - UInt128[] wideCycles) - { - SmallDivisors = smallDivisors; - LargeDivisors = largeDivisors; - VeryLargeDivisors = veryLargeDivisors; - WideModuli = wideModuli; - WideExponents = wideExponents; - SmallExponents = smallExponents; - LargeExponents = largeExponents; - VeryLargeExponents = veryLargeExponents; - SmallCycles = smallCycles; - LargeCycles = largeCycles; - VeryLargeCycles = veryLargeCycles; - WideCycles = wideCycles; - } + var smallDivisors = new MontgomeryDivisorData[SampleCount]; + var largeDivisors = new MontgomeryDivisorData[SampleCount]; + var veryLargeDivisors = new MontgomeryDivisorData[SampleCount]; + var wideModuli = new UInt128[SampleCount]; + var wideExponents = new UInt128[SampleCount]; + var smallExponents = new ulong[SampleCount]; + var largeExponents = new ulong[SampleCount]; + var veryLargeExponents = new ulong[SampleCount]; + var smallCycles = new ulong[SampleCount]; + var largeCycles = new ulong[SampleCount]; + var veryLargeCycles = new ulong[SampleCount]; + var wideCycles = new UInt128[SampleCount]; - public MontgomeryDivisorData[] SmallDivisors { get; } - public MontgomeryDivisorData[] LargeDivisors { get; } - public MontgomeryDivisorData[] VeryLargeDivisors { get; } - public UInt128[] WideModuli { get; } - public UInt128[] WideExponents { get; } - public ulong[] SmallExponents { get; } - public ulong[] LargeExponents { get; } - public ulong[] VeryLargeExponents { get; } - public ulong[] SmallCycles { get; } - public ulong[] LargeCycles { get; } - public ulong[] VeryLargeCycles { get; } - public UInt128[] WideCycles { get; } - } + ulong? previousPrimeOrder = null; + UInt128? previousWidePrimeOrder = null; - private static class SampleCollectionsProvider - { - private static readonly Lazy Cache = new(Create); - - public static SampleCollections Instance => Cache.Value; - - private static SampleCollections Create() - { - Console.WriteLine("Preparing power-of-two modulus benchmark sample collections..."); - - Random random = new(13); - Random heuristicRandom = new(113); - Random wideRandom = new(19); - - var smallDivisors = new MontgomeryDivisorData[SampleCount]; - var largeDivisors = new MontgomeryDivisorData[SampleCount]; - var veryLargeDivisors = new MontgomeryDivisorData[SampleCount]; - var wideModuli = new UInt128[SampleCount]; - var wideExponents = new UInt128[SampleCount]; - var smallExponents = new ulong[SampleCount]; - var largeExponents = new ulong[SampleCount]; - var veryLargeExponents = new ulong[SampleCount]; - var smallCycles = new ulong[SampleCount]; - var largeCycles = new ulong[SampleCount]; - var veryLargeCycles = new ulong[SampleCount]; - var wideCycles = new UInt128[SampleCount]; - - ulong? previousPrimeOrder = null; - UInt128? previousWidePrimeOrder = null; - - for (int i = 0; i < SampleCount; i++) - { - ulong smallModulus = NextSmallOddModulus(random); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + ulong smallModulus = NextSmallOddModulus(random); Console.WriteLine($"Calculating small cycle {smallModulus}"); - smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus); - smallExponents[i] = NextSmallExponent(random); - smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus)); + smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus); + smallExponents[i] = NextSmallExponent(random); + smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(gpu, smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus)); - (ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle(random); + (ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle(random); Console.WriteLine($"Calculating large cycle {largeModulus}"); - largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus); - largeExponents[i] = NextLargeExponent(random); - largeCycles[i] = largeCycle; + largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus); + largeExponents[i] = NextLargeExponent(random); + largeCycles[i] = largeCycle; - ulong veryLargeModulus = NextVeryLargeOddModulus(heuristicRandom); + ulong veryLargeModulus = NextVeryLargeOddModulus(heuristicRandom); Console.WriteLine($"Calculating very large cycle {veryLargeModulus} with heuristics"); - veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus); - veryLargeExponents[i] = NextVeryLargeExponent(heuristicRandom); - veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus, ref previousPrimeOrder); + veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus); + veryLargeExponents[i] = NextVeryLargeExponent(heuristicRandom); + veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(gpu, veryLargeModulus, ref previousPrimeOrder); - UInt128 wideModulus = NextWideOddModulus(wideRandom); + UInt128 wideModulus = NextWideOddModulus(wideRandom); Console.WriteLine($"Calculating wide cycle {wideModulus} with heuristics"); - wideModuli[i] = wideModulus; - wideExponents[i] = NextWideExponent(wideRandom); - wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus, ref previousWidePrimeOrder); - } + wideModuli[i] = wideModulus; + wideExponents[i] = NextWideExponent(wideRandom); + wideCycles[i] = CalculateCycleLengthWithHeuristics(gpu, wideModulus, ref previousWidePrimeOrder); + } - SampleCollections collections = new SampleCollections( - smallDivisors, - largeDivisors, - veryLargeDivisors, - wideModuli, - wideExponents, - smallExponents, - largeExponents, - veryLargeExponents, - smallCycles, - largeCycles, - veryLargeCycles, - wideCycles); + SampleCollections collections = new SampleCollections( + smallDivisors, + largeDivisors, + veryLargeDivisors, + wideModuli, + wideExponents, + smallExponents, + largeExponents, + veryLargeExponents, + smallCycles, + largeCycles, + veryLargeCycles, + wideCycles); - Console.WriteLine("Finished preparing power-of-two modulus benchmark sample collections."); + Console.WriteLine("Finished preparing power-of-two modulus benchmark sample collections."); - return collections; - } + PrimeOrderCalculatorAccelerator.Return(gpu); + return collections; + } - private static ulong NextSmallExponent(Random random) => (ulong)random.NextInt64(1L, 1_000_000L); + private static ulong NextSmallExponent(Random random) => (ulong)random.NextInt64(1L, 1_000_000L); - private static ulong NextLargeExponent(Random random) => (ulong)random.NextInt64(OneShiftedLeft60, long.MaxValue); + private static ulong NextLargeExponent(Random random) => (ulong)random.NextInt64(OneShiftedLeft60, long.MaxValue); - private static ulong NextVeryLargeExponent(Random random) - { - Span buffer = stackalloc byte[8]; - random.NextBytes(buffer); - ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63); - return value; - } + private static ulong NextVeryLargeExponent(Random random) + { + Span buffer = stackalloc byte[8]; + random.NextBytes(buffer); + ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63); + return value; + } - private static ulong NextSmallOddModulus(Random random) - { - ulong value = (ulong)random.NextInt64(3L, 1_000_000L); - return value | 1UL; - } + private static ulong NextSmallOddModulus(Random random) + { + ulong value = (ulong)random.NextInt64(3L, 1_000_000L); + return value | 1UL; + } - private static (ulong Modulus, ulong CycleLength) NextLargeModulusAndCycle(Random random) - { - int bitLength = random.Next(48, 64); - ulong modulus = (1UL << bitLength) - 1UL; - return (modulus, (ulong)bitLength); - } + private static (ulong Modulus, ulong CycleLength) NextLargeModulusAndCycle(Random random) + { + int bitLength = random.Next(48, 64); + ulong modulus = (1UL << bitLength) - 1UL; + return (modulus, (ulong)bitLength); + } - private static ulong NextVeryLargeOddModulus(Random random) - { - Span buffer = stackalloc byte[8]; - random.NextBytes(buffer); - ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL; - return value; - } + private static ulong NextVeryLargeOddModulus(Random random) + { + Span buffer = stackalloc byte[8]; + random.NextBytes(buffer); + ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL; + return value; + } - private static UInt128 NextWideOddModulus(Random random) - { - Span buffer = stackalloc byte[16]; - random.NextBytes(buffer); + private static UInt128 NextWideOddModulus(Random random) + { + Span buffer = stackalloc byte[16]; + random.NextBytes(buffer); - ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer); - ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]); - if (high == 0UL) - { - high = 1UL; - } + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]); + if (high == 0UL) + { + high = 1UL; + } - UInt128 modulus = ((UInt128)high << 64) | low; - modulus |= UInt128.One; + UInt128 modulus = ((UInt128)high << 64) | low; + modulus |= UInt128.One; - if (modulus <= UInt128.One) - { - modulus += 2UL; - } + if (modulus <= UInt128.One) + { + modulus += 2UL; + } - return modulus; - } + return modulus; + } - private static UInt128 NextWideExponent(Random random) - { - Span buffer = stackalloc byte[16]; - random.NextBytes(buffer); - ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer); - ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]); - if (high == 0UL) - { - high = 1UL; - } + private static UInt128 NextWideExponent(Random random) + { + Span buffer = stackalloc byte[16]; + random.NextBytes(buffer); + ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer); + ulong high = BinaryPrimitives.ReadUInt64LittleEndian(buffer[8..]); + if (high == 0UL) + { + high = 1UL; + } - return ((UInt128)high << 64) | low; - } + return ((UInt128)high << 64) | low; + } - private static ulong CalculateCycleLengthWithHeuristics(ulong modulus, ref ulong? previousPrimeOrder) - { - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(modulus); - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData); - } + private static ulong CalculateCycleLengthWithHeuristics(PrimeOrderCalculatorAccelerator gpu, ulong modulus, ref ulong? previousPrimeOrder) + { + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(modulus); + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisorData); + } - ulong order = PrimeOrderCalculator.Calculate( - modulus, - previousPrimeOrder, - divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu); + ulong order = PrimeOrderCalculator.Calculate( + gpu, + modulus, + previousPrimeOrder, + divisorData, + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu); - if (order != 0UL) - { - previousPrimeOrder = order; - return order; - } + if (order != 0UL) + { + previousPrimeOrder = order; + return order; + } - previousPrimeOrder = null; - return MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData); - } + previousPrimeOrder = null; + return MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisorData); + } - private static UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus, ref UInt128? previousWidePrimeOrder) - { - if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero) - { - return MersenneDivisorCycles.GetCycle(modulus); - } + private static UInt128 CalculateCycleLengthWithHeuristics(PrimeOrderCalculatorAccelerator gpu, UInt128 modulus, ref UInt128? previousWidePrimeOrder) + { + if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero) + { + return MersenneDivisorCycles.GetCycle(gpu, modulus); + } - UInt128 order = PrimeOrderCalculator.Calculate( - modulus, - previousWidePrimeOrder, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu); + UInt128 order = PrimeOrderCalculator.Calculate( + gpu, + modulus, + previousWidePrimeOrder, + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu); - if (order != UInt128.Zero) - { - previousWidePrimeOrder = order; - return order; - } + if (order != UInt128.Zero) + { + previousWidePrimeOrder = order; + return order; + } - previousWidePrimeOrder = null; - return MersenneDivisorCycles.GetCycle(modulus); - } - } + previousWidePrimeOrder = null; + return MersenneDivisorCycles.GetCycle(gpu, modulus); + } + } - private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus) - { - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL); - } + private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus) + { + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL); + } - ulong nPrime = ComputeMontgomeryNPrime(modulus); - ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus); - ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus); - ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime); + ulong nPrime = ComputeMontgomeryNPrime(modulus); + ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus); + ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus); + ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime); - return new MontgomeryDivisorData( - modulus, - nPrime, - montgomeryOne, - montgomeryTwo, - montgomeryTwoSquared); - } + return new MontgomeryDivisorData( + modulus, + nPrime, + montgomeryOne, + montgomeryTwo, + montgomeryTwoSquared); + } - private ulong CalculateCycleLengthWithHeuristics(ulong modulus, ulong fallbackOrder = 0UL) - { - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(modulus); - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData); - } + private ulong CalculateCycleLengthWithHeuristics(PrimeOrderCalculatorAccelerator gpu, ulong modulus, ulong fallbackOrder = 0UL) + { + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(modulus); + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisorData); + } #if DEBUG - Console.WriteLine("Trying heuristic. Prime order calculation"); + Console.WriteLine("Trying heuristic. Prime order calculation"); #endif - ulong order = PrimeOrderCalculator.Calculate( - modulus, - _previousPrimeOrder, - divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu); + ulong order = PrimeOrderCalculator.Calculate( + gpu, + modulus, + _previousPrimeOrder, + divisorData, + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu); - if (order != 0UL) - { - _previousPrimeOrder = order; - return order; - } + if (order != 0UL) + { + _previousPrimeOrder = order; + return order; + } - _previousPrimeOrder = null; + _previousPrimeOrder = null; - Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation"); - return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData); - } + Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation"); + return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisorData); + } - private UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus) - { - if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero) - { - return MersenneDivisorCycles.GetCycle(modulus); - } + private UInt128 CalculateCycleLengthWithHeuristics(PrimeOrderCalculatorAccelerator gpu, UInt128 modulus) + { + if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero) + { + return MersenneDivisorCycles.GetCycle(gpu, modulus); + } #if DEBUG - Console.WriteLine("Trying heuristic. Wide prime order calculation"); + Console.WriteLine("Trying heuristic. Wide prime order calculation"); #endif - UInt128 order = PrimeOrderCalculator.Calculate( - modulus, - _previousWidePrimeOrder, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu); - - if (order != UInt128.Zero) - { - _previousWidePrimeOrder = order; - return order; - } - - _previousWidePrimeOrder = null; - - Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation"); - return MersenneDivisorCycles.GetCycle(modulus); - } - - private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus); - - private static ulong ComputeMontgomeryNPrime(ulong modulus) - { - ulong inv = modulus; - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - return unchecked(0UL - inv); - } - - private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor) - { - ulong modulus = divisor.Modulus; - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } - - ulong result = divisor.MontgomeryOne; - ulong baseVal = divisor.MontgomeryTwo; - ulong nPrime = divisor.NPrime; - - if (exponent == 0UL) - { - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } - - int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent); - - for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--) - { - result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); - - if (((exponent >> bitIndex) & 1UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime); - } - } - - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } - - private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor) - { - ulong modulus = divisor.Modulus; - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } - - ulong result = divisor.MontgomeryOne; - ulong baseVal = divisor.MontgomeryTwo; - ulong nPrime = divisor.NPrime; - ulong remainingExponent = exponent; - - while (remainingExponent != 0UL) - { - ulong nibble = remainingExponent & 0xFUL; - - ulong base0 = baseVal; - ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime); - ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime); - ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime); - - if ((nibble & 1UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime); - } - - if ((nibble & 2UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime); - } - - if ((nibble & 4UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime); - } - - if ((nibble & 8UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime); - } - - baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime); - remainingExponent >>= 4; - } - - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } - - private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor) - { - ulong modulus = divisor.Modulus; - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } - - ulong nPrime = divisor.NPrime; - ulong result = divisor.MontgomeryOne; - ulong baseVal = divisor.MontgomeryTwo; - - if (exponent == 0UL) - { - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } - - const int WindowSize = 5; - Span oddPowers = stackalloc ulong[1 << (WindowSize - 1)]; - oddPowers[0] = baseVal; - ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime); - for (int i = 1; i < oddPowers.Length; i++) - { - oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime); - } - - int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent); - - while (bitIndex >= 0) - { - if (((exponent >> bitIndex) & 1UL) == 0UL) - { - result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); - bitIndex--; - continue; - } - - int windowStart = Math.Max(bitIndex - WindowSize + 1, 0); - ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL); - - while ((windowValue & 1UL) == 0UL) - { - windowValue >>= 1; - windowStart++; - } - - int squares = bitIndex - windowStart + 1; - for (int i = 0; i < squares; i++) - { - result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); - } - - result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime); - bitIndex = windowStart - 1; - } - - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } - - private static ulong Pow2ModBinary(ulong exponent, ulong modulus) - { - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } - - ulong result = 1UL % modulus; - ulong baseVal = 2UL % modulus; - ulong remainingExponent = exponent; - - while (remainingExponent > 0UL) - { - if ((remainingExponent & 1UL) != 0UL) - { - result = MultiplyMod(result, baseVal, modulus); - } - - remainingExponent >>= 1; - if (remainingExponent == 0UL) - { - break; - } - - baseVal = MultiplyMod(baseVal, baseVal, modulus); - } - - return result; - } - - private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus) - { - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } - - if (exponent == 0UL) - { - return 1UL % modulus; - } - - Span powers = stackalloc ulong[64]; - powers[0] = 2UL % modulus; - int highestBit = 63 - BitOperations.LeadingZeroCount(exponent); - - for (int index = 1; index <= highestBit; index++) - { - powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus); - } - - ulong result = 1UL % modulus; - ulong remainingExponent = exponent; - int bitIndex = 0; - - while (remainingExponent > 0UL) - { - if ((remainingExponent & 1UL) != 0UL) - { - result = MultiplyMod(result, powers[bitIndex], modulus); - } - - remainingExponent >>= 1; - bitIndex++; - } - - return result; - } - - private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus) - { - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } - - BigInteger mod = modulus; - BigInteger result = BigInteger.ModPow(2, exponent, mod); - return (ulong)result; - } - - private static UInt128 Pow2MontgomeryModWindowedGpuShim(UInt128 exponent, UInt128 modulus) - { - if (modulus == UInt128.One) - { - return UInt128.Zero; - } - - if (exponent == UInt128.Zero) - { - return UInt128.One % modulus; - } - - GpuUInt128 modulusGpu = new(modulus); - GpuUInt128 exponentGpu = new(exponent); - GpuUInt128 baseValue = new(2UL); - if (baseValue.CompareTo(modulusGpu) >= 0) - { - baseValue.Sub(modulusGpu); - } - - if (ShouldUseSingleBit(exponentGpu)) - { - GpuUInt128 singleBitResult = Pow2MontgomeryModSingleBit(exponentGpu, modulusGpu, baseValue); - return (UInt128)singleBitResult; - } - - int bitLength = exponentGpu.GetBitLength(); - int windowSize = GetWindowSize(bitLength); - int oddPowerCount = 1 << (windowSize - 1); - - Span oddPowers = stackalloc GpuUInt128[oddPowerCount]; - InitializeOddPowers(baseValue, modulusGpu, oddPowers); - - GpuUInt128 result = GpuUInt128.One; - int index = bitLength - 1; - - while (index >= 0) - { - if (!IsBitSet(exponentGpu, index)) - { - result.MulMod(result, modulusGpu); - index--; - continue; - } - - int windowStart = index - windowSize + 1; - if (windowStart < 0) - { - windowStart = 0; - } - - while (!IsBitSet(exponentGpu, windowStart)) - { - windowStart++; - } - - int windowBitCount = index - windowStart + 1; - for (int square = 0; square < windowBitCount; square++) - { - result.MulMod(result, modulusGpu); - } - - ulong windowValue = ExtractWindowValue(exponentGpu, windowStart, windowBitCount); - int tableIndex = (int)((windowValue - 1UL) >> 1); - GpuUInt128 factor = oddPowers[tableIndex]; - result.MulMod(factor, modulusGpu); - - index = windowStart - 1; - } - - return (UInt128)result; - } - - private static bool ShouldUseSingleBit(GpuUInt128 exponent) - { - if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold) - { - return true; - } - - return false; - } - - private static int GetWindowSize(int bitLength) - { - if (bitLength <= 8) - { - return Math.Max(bitLength, 1); - } - - if (bitLength <= 23) - { - return 4; - } - - if (bitLength <= 79) - { - return 5; - } - - if (bitLength <= 239) - { - return 6; - } - - if (bitLength <= 671) - { - return 7; - } - - return 8; - } - - private static void InitializeOddPowers(GpuUInt128 baseValue, GpuUInt128 modulus, Span oddPowers) - { - oddPowers[0] = baseValue; - if (oddPowers.Length == 1) - { - return; - } - - baseValue.MulMod(baseValue, modulus); - for (int i = 1; i < oddPowers.Length; i++) - { - oddPowers[i] = oddPowers[i - 1]; - oddPowers[i].MulMod(baseValue, modulus); - } - } - - private static GpuUInt128 Pow2MontgomeryModSingleBit(GpuUInt128 exponent, GpuUInt128 modulus, GpuUInt128 baseValue) - { - GpuUInt128 result = GpuUInt128.One; - - while (!exponent.IsZero) - { - if ((exponent.Low & 1UL) != 0UL) - { - result.MulMod(baseValue, modulus); - } - - exponent.ShiftRight(1); - if (exponent.IsZero) - { - break; - } - - baseValue.MulMod(baseValue, modulus); - } - - return result; - } - - private static bool IsBitSet(GpuUInt128 value, int bitIndex) - { - if (bitIndex >= 64) - { - return ((value.High >> (bitIndex - 64)) & 1UL) != 0UL; - } - - return ((value.Low >> bitIndex) & 1UL) != 0UL; - } - - private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount) - { - if (windowStart != 0) - { - GpuUInt128 shifted = exponent; - shifted.ShiftRight(windowStart); - ulong mask = (1UL << windowBitCount) - 1UL; - return shifted.Low & mask; - } - - ulong directMask = (1UL << windowBitCount) - 1UL; - return exponent.Low & directMask; - } - - private static ulong MultiplyMod(ulong x, ulong y, ulong modulus) - { - UInt128 product = (UInt128)x * y; - return (ulong)(product % modulus); - } + UInt128 order = PrimeOrderCalculator.Calculate( + gpu, + modulus, + _previousWidePrimeOrder, + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu); + + if (order != UInt128.Zero) + { + _previousWidePrimeOrder = order; + return order; + } + + _previousWidePrimeOrder = null; + + Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation"); + return MersenneDivisorCycles.GetCycle(gpu, modulus); + } + + private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus); + + private static ulong ComputeMontgomeryNPrime(ulong modulus) + { + ulong inv = modulus; + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + return unchecked(0UL - inv); + } + + private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor) + { + ulong modulus = divisor.Modulus; + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } + + ulong result = divisor.MontgomeryOne; + ulong baseVal = divisor.MontgomeryTwo; + ulong nPrime = divisor.NPrime; + + if (exponent == 0UL) + { + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } + + int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent); + + for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--) + { + result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); + + if (((exponent >> bitIndex) & 1UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime); + } + } + + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } + + private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor) + { + ulong modulus = divisor.Modulus; + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } + + ulong result = divisor.MontgomeryOne; + ulong baseVal = divisor.MontgomeryTwo; + ulong nPrime = divisor.NPrime; + ulong remainingExponent = exponent; + + while (remainingExponent != 0UL) + { + ulong nibble = remainingExponent & 0xFUL; + + ulong base0 = baseVal; + ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime); + ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime); + ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime); + + if ((nibble & 1UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime); + } + + if ((nibble & 2UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime); + } + + if ((nibble & 4UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime); + } + + if ((nibble & 8UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime); + } + + baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime); + remainingExponent >>= 4; + } + + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } + + private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor) + { + ulong modulus = divisor.Modulus; + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } + + ulong nPrime = divisor.NPrime; + ulong result = divisor.MontgomeryOne; + ulong baseVal = divisor.MontgomeryTwo; + + if (exponent == 0UL) + { + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } + + const int WindowSize = 5; + Span oddPowers = stackalloc ulong[1 << (WindowSize - 1)]; + oddPowers[0] = baseVal; + ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime); + for (int i = 1; i < oddPowers.Length; i++) + { + oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime); + } + + int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent); + + while (bitIndex >= 0) + { + if (((exponent >> bitIndex) & 1UL) == 0UL) + { + result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); + bitIndex--; + continue; + } + + int windowStart = Math.Max(bitIndex - WindowSize + 1, 0); + ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL); + + while ((windowValue & 1UL) == 0UL) + { + windowValue >>= 1; + windowStart++; + } + + int squares = bitIndex - windowStart + 1; + for (int i = 0; i < squares; i++) + { + result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); + } + + result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime); + bitIndex = windowStart - 1; + } + + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } + + private static ulong Pow2ModBinary(ulong exponent, ulong modulus) + { + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } + + ulong result = 1UL % modulus; + ulong baseVal = 2UL % modulus; + ulong remainingExponent = exponent; + + while (remainingExponent > 0UL) + { + if ((remainingExponent & 1UL) != 0UL) + { + result = MultiplyMod(result, baseVal, modulus); + } + + remainingExponent >>= 1; + if (remainingExponent == 0UL) + { + break; + } + + baseVal = MultiplyMod(baseVal, baseVal, modulus); + } + + return result; + } + + private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus) + { + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } + + if (exponent == 0UL) + { + return 1UL % modulus; + } + + Span powers = stackalloc ulong[64]; + powers[0] = 2UL % modulus; + int highestBit = 63 - BitOperations.LeadingZeroCount(exponent); + + for (int index = 1; index <= highestBit; index++) + { + powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus); + } + + ulong result = 1UL % modulus; + ulong remainingExponent = exponent; + int bitIndex = 0; + + while (remainingExponent > 0UL) + { + if ((remainingExponent & 1UL) != 0UL) + { + result = MultiplyMod(result, powers[bitIndex], modulus); + } + + remainingExponent >>= 1; + bitIndex++; + } + + return result; + } + + private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus) + { + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } + + BigInteger mod = modulus; + BigInteger result = BigInteger.ModPow(2, exponent, mod); + return (ulong)result; + } + + private static UInt128 Pow2MontgomeryModWindowedGpuShim(UInt128 exponent, UInt128 modulus) + { + if (modulus == UInt128.One) + { + return UInt128.Zero; + } + + if (exponent == UInt128.Zero) + { + return UInt128.One % modulus; + } + + GpuUInt128 modulusGpu = new(modulus); + GpuUInt128 exponentGpu = new(exponent); + GpuUInt128 baseValue = new(2UL); + if (baseValue.CompareTo(modulusGpu) >= 0) + { + baseValue.Sub(modulusGpu); + } + + if (ShouldUseSingleBit(exponentGpu)) + { + GpuUInt128 singleBitResult = Pow2MontgomeryModSingleBit(exponentGpu, modulusGpu, baseValue); + return (UInt128)singleBitResult; + } + + int bitLength = exponentGpu.GetBitLength(); + int windowSize = GetWindowSize(bitLength); + int oddPowerCount = 1 << (windowSize - 1); + + Span oddPowers = stackalloc GpuUInt128[oddPowerCount]; + InitializeOddPowers(baseValue, modulusGpu, oddPowers); + + GpuUInt128 result = GpuUInt128.One; + int index = bitLength - 1; + + while (index >= 0) + { + if (!IsBitSet(exponentGpu, index)) + { + result.MulMod(result, modulusGpu); + index--; + continue; + } + + int windowStart = index - windowSize + 1; + if (windowStart < 0) + { + windowStart = 0; + } + + while (!IsBitSet(exponentGpu, windowStart)) + { + windowStart++; + } + + int windowBitCount = index - windowStart + 1; + for (int square = 0; square < windowBitCount; square++) + { + result.MulMod(result, modulusGpu); + } + + ulong windowValue = ExtractWindowValue(exponentGpu, windowStart, windowBitCount); + int tableIndex = (int)((windowValue - 1UL) >> 1); + GpuUInt128 factor = oddPowers[tableIndex]; + result.MulMod(factor, modulusGpu); + + index = windowStart - 1; + } + + return (UInt128)result; + } + + private static bool ShouldUseSingleBit(GpuUInt128 exponent) + { + if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold) + { + return true; + } + + return false; + } + + private static int GetWindowSize(int bitLength) + { + if (bitLength <= 8) + { + return Math.Max(bitLength, 1); + } + + if (bitLength <= 23) + { + return 4; + } + + if (bitLength <= 79) + { + return 5; + } + + if (bitLength <= 239) + { + return 6; + } + + if (bitLength <= 671) + { + return 7; + } + + return 8; + } + + private static void InitializeOddPowers(GpuUInt128 baseValue, GpuUInt128 modulus, Span oddPowers) + { + oddPowers[0] = baseValue; + if (oddPowers.Length == 1) + { + return; + } + + baseValue.MulMod(baseValue, modulus); + for (int i = 1; i < oddPowers.Length; i++) + { + oddPowers[i] = oddPowers[i - 1]; + oddPowers[i].MulMod(baseValue, modulus); + } + } + + private static GpuUInt128 Pow2MontgomeryModSingleBit(GpuUInt128 exponent, GpuUInt128 modulus, GpuUInt128 baseValue) + { + GpuUInt128 result = GpuUInt128.One; + + while (!exponent.IsZero) + { + if ((exponent.Low & 1UL) != 0UL) + { + result.MulMod(baseValue, modulus); + } + + exponent.ShiftRight(1); + if (exponent.IsZero) + { + break; + } + + baseValue.MulMod(baseValue, modulus); + } + + return result; + } + + private static bool IsBitSet(GpuUInt128 value, int bitIndex) + { + if (bitIndex >= 64) + { + return ((value.High >> (bitIndex - 64)) & 1UL) != 0UL; + } + + return ((value.Low >> bitIndex) & 1UL) != 0UL; + } + + private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount) + { + if (windowStart != 0) + { + GpuUInt128 shifted = exponent; + shifted.ShiftRight(windowStart); + ulong mask = (1UL << windowBitCount) - 1UL; + return shifted.Low & mask; + } + + ulong directMask = (1UL << windowBitCount) - 1UL; + return exponent.Low & directMask; + } + + private static ulong MultiplyMod(ulong x, ulong y, ulong modulus) + { + UInt128 product = (UInt128)x * y; + return (ulong)(product % modulus); + } } diff --git a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModBenchmarks.cs index 5d11eda..5a3b629 100644 --- a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModBenchmarks.cs @@ -2,6 +2,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using System.Numerics; using PerfectNumbers.Core; +using PerfectNumbers.Core.Gpu.Accelerators; namespace EvenPerfectBitScanner.Benchmarks; @@ -9,43 +10,46 @@ namespace EvenPerfectBitScanner.Benchmarks; [SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)] public class Pow2MontgomeryModBenchmarks { - private const int SampleCount = 256; + private const int SampleCount = 256; - private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount]; - private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount]; - private readonly ulong[] _smallExponents = new ulong[SampleCount]; - private readonly ulong[] _largeExponents = new ulong[SampleCount]; - private readonly ulong[] _smallCycles = new ulong[SampleCount]; - private readonly ulong[] _largeCycles = new ulong[SampleCount]; + private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount]; + private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount]; + private readonly ulong[] _smallExponents = new ulong[SampleCount]; + private readonly ulong[] _largeExponents = new ulong[SampleCount]; + private readonly ulong[] _smallCycles = new ulong[SampleCount]; + private readonly ulong[] _largeCycles = new ulong[SampleCount]; - private readonly Random _random = new(13); + private readonly Random _random = new(13); - public enum InputScale - { - Small, - Large - } + public enum InputScale + { + Small, + Large + } - [ParamsAllValues] - public InputScale Scale { get; set; } + [ParamsAllValues] + public InputScale Scale { get; set; } - [GlobalSetup] - public void Setup() - { - for (int i = 0; i < SampleCount; i++) - { - ulong smallModulus = NextSmallOddModulus(); - _smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus); - _smallExponents[i] = NextSmallExponent(); - _smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus)); + [GlobalSetup] + public void Setup() + { + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + ulong smallModulus = NextSmallOddModulus(); + _smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus); + _smallExponents[i] = NextSmallExponent(); + _smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(gpu, smallModulus, MontgomeryDivisorDataPool.Shared.FromModulus(smallModulus)); - Console.WriteLine($"Calculating large cycle {i + 1} with heuristics"); - (ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle(); + Console.WriteLine($"Calculating large cycle {i + 1} with heuristics"); + (ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle(); _largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus); - _largeExponents[i] = NextLargeExponent(); - _largeCycles[i] = largeCycle; - } - } + _largeExponents[i] = NextLargeExponent(); + _largeCycles[i] = largeCycle; + } + + PrimeOrderCalculatorAccelerator.Return(gpu); + } /// /// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one. @@ -64,455 +68,459 @@ public class Pow2MontgomeryModBenchmarks return checksum; } - /// - /// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one. + /// + /// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one. /// /// This benchmark failed running, hanging up early before reaching workload testing. - /// - // [Benchmark] - public ulong BaselineGpu() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + // [Benchmark] + public ulong BaselineGpu() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(gpu, divisors[i]); + } - return checksum; - } + PrimeOrderCalculatorAccelerator.Return(gpu); + return checksum; + } - /// - /// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline). - /// - [Benchmark] - public ulong BaselineWithKnownCycleCpu() - { - GetData(out var exponents, out var divisors, out var cycles); - ulong checksum = 0UL; + /// + /// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline). + /// + [Benchmark] + public ulong BaselineWithKnownCycleCpu() + { + GetData(out var exponents, out var divisors, out var cycles); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]); + } - return checksum; - } + return checksum; + } - /// - /// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline). - /// - [Benchmark] - public ulong BaselineWithKnownCycleGpu() - { - GetData(out var exponents, out var divisors, out var cycles); - ulong checksum = 0UL; + /// + /// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline). + /// + [Benchmark] + public ulong BaselineWithKnownCycleGpu() + { + GetData(out var exponents, out var divisors, out var cycles); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + for (int i = 0; i < SampleCount; i++) + { + checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycles[i], divisors[i]); + } - return checksum; - } + PrimeOrderCalculatorAccelerator.Return(gpu); + return checksum; + } - /// - /// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×). - /// - [Benchmark] - public ulong LeftToRight() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Left-to-right Montgomery scan that trails the baseline: 39.11 μs on small inputs (1.08×) and 126.38 μs on large ones (1.08×). + /// + [Benchmark] + public ulong LeftToRight() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2MontgomeryModLeftToRight(exponents[i], divisors[i]); + } - return checksum; - } + return checksum; + } - /// - /// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×). - /// - [Benchmark] - public ulong Batched4() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Processes four exponent bits per loop, finishing at 38.74 μs on small cases (1.07× baseline) and 120.11 μs on large ones (1.02×). + /// + [Benchmark] + public ulong Batched4() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2MontgomeryModBatched4(exponents[i], divisors[i]); + } - return checksum; - } + return checksum; + } - /// - /// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×). - /// - [Benchmark] - public ulong SlidingWindow() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Windowed Montgomery ladder (width 5) that lags behind the baseline at 52.82 μs on small samples (1.45×) and 124.13 μs on large samples (1.06×). + /// + [Benchmark] + public ulong SlidingWindow() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2MontgomeryModSlidingWindow(exponents[i], divisors[i]); + } - return checksum; - } + return checksum; + } - /// - /// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×). - /// - [Benchmark] - public ulong BinaryMod() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Classic binary ladder without Montgomery reduction; runs 24.75 μs on small inputs (0.68× baseline) but collapses to 295.29 μs on large ones (2.52×). + /// + [Benchmark] + public ulong BinaryMod() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2ModBinary(exponents[i], divisors[i].Modulus); + } - return checksum; - } + return checksum; + } - /// - /// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×). - /// - [Benchmark] - public ulong PrecomputedTableMod() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Precomputes all squarings once; 30.18 μs on small inputs (0.83× baseline) but 289.80 μs on large ones (2.47×). + /// + [Benchmark] + public ulong PrecomputedTableMod() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2ModPrecomputedTable(exponents[i], divisors[i].Modulus); + } - return checksum; - } + return checksum; + } - /// - /// Relies on ; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×). - /// - [Benchmark] - public ulong BigIntegerMod() - { - GetData(out var exponents, out var divisors, out _); - ulong checksum = 0UL; + /// + /// Relies on ; fastest on small inputs at 17.21 μs (0.47× baseline) but slowest on large ones at 562.78 μs (4.80×). + /// + [Benchmark] + public ulong BigIntegerMod() + { + GetData(out var exponents, out var divisors, out _); + ulong checksum = 0UL; - for (int i = 0; i < SampleCount; i++) - { - checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus); - } + for (int i = 0; i < SampleCount; i++) + { + checksum ^= Pow2ModBigInteger(exponents[i], divisors[i].Modulus); + } - return checksum; - } + return checksum; + } - private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles) - { - if (Scale == InputScale.Small) - { - exponents = _smallExponents; - divisors = _smallDivisors; - cycles = _smallCycles; - } - else - { - exponents = _largeExponents; - divisors = _largeDivisors; - cycles = _largeCycles; - } - } + private void GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles) + { + if (Scale == InputScale.Small) + { + exponents = _smallExponents; + divisors = _smallDivisors; + cycles = _smallCycles; + } + else + { + exponents = _largeExponents; + divisors = _largeDivisors; + cycles = _largeCycles; + } + } - private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L); + private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L); - private ulong NextLargeExponent() => (ulong)_random.NextInt64(1L << 60, long.MaxValue); + private ulong NextLargeExponent() => (ulong)_random.NextInt64(1L << 60, long.MaxValue); - private ulong NextSmallOddModulus() - { - ulong value = (ulong)_random.NextInt64(3L, 1_000_000L); - return value | 1UL; - } + private ulong NextSmallOddModulus() + { + ulong value = (ulong)_random.NextInt64(3L, 1_000_000L); + return value | 1UL; + } - private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle() - { - int bitLength = _random.Next(48, 64); - ulong modulus = (1UL << bitLength) - 1UL; - return (modulus, (ulong)bitLength); - } + private (ulong modulus, ulong cycleLength) NextLargeModulusAndCycle() + { + int bitLength = _random.Next(48, 64); + ulong modulus = (1UL << bitLength) - 1UL; + return (modulus, (ulong)bitLength); + } - private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus) - { - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL); - } + private static MontgomeryDivisorData CreateMontgomeryDivisorData(ulong modulus) + { + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return new MontgomeryDivisorData(modulus, 0UL, 0UL, 0UL, 0UL); + } - ulong nPrime = ComputeMontgomeryNPrime(modulus); - ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus); - ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus); - ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime); + ulong nPrime = ComputeMontgomeryNPrime(modulus); + ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus); + ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus); + ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime); - return new MontgomeryDivisorData( - modulus, - nPrime, - montgomeryOne, - montgomeryTwo, - montgomeryTwoSquared); - } + return new MontgomeryDivisorData( + modulus, + nPrime, + montgomeryOne, + montgomeryTwo, + montgomeryTwoSquared); + } - private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus); + private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus); - private static ulong ComputeMontgomeryNPrime(ulong modulus) - { - ulong inv = modulus; - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - return unchecked(0UL - inv); - } + private static ulong ComputeMontgomeryNPrime(ulong modulus) + { + ulong inv = modulus; + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + return unchecked(0UL - inv); + } - private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor) - { - ulong modulus = divisor.Modulus; - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } + private static ulong Pow2MontgomeryModLeftToRight(ulong exponent, in MontgomeryDivisorData divisor) + { + ulong modulus = divisor.Modulus; + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } - ulong result = divisor.MontgomeryOne; - ulong baseVal = divisor.MontgomeryTwo; - ulong nPrime = divisor.NPrime; + ulong result = divisor.MontgomeryOne; + ulong baseVal = divisor.MontgomeryTwo; + ulong nPrime = divisor.NPrime; - if (exponent == 0UL) - { - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } + if (exponent == 0UL) + { + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } - int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent); + int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent); - for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--) - { - result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); + for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--) + { + result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); - if (((exponent >> bitIndex) & 1UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime); - } - } + if (((exponent >> bitIndex) & 1UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime); + } + } - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } - private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor) - { - ulong modulus = divisor.Modulus; - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } + private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor) + { + ulong modulus = divisor.Modulus; + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } - ulong result = divisor.MontgomeryOne; - ulong baseVal = divisor.MontgomeryTwo; - ulong nPrime = divisor.NPrime; - ulong remainingExponent = exponent; + ulong result = divisor.MontgomeryOne; + ulong baseVal = divisor.MontgomeryTwo; + ulong nPrime = divisor.NPrime; + ulong remainingExponent = exponent; - while (remainingExponent != 0UL) - { - ulong nibble = remainingExponent & 0xFUL; + while (remainingExponent != 0UL) + { + ulong nibble = remainingExponent & 0xFUL; - ulong base0 = baseVal; - ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime); - ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime); - ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime); + ulong base0 = baseVal; + ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime); + ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime); + ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime); - if ((nibble & 1UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime); - } + if ((nibble & 1UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime); + } - if ((nibble & 2UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime); - } + if ((nibble & 2UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime); + } - if ((nibble & 4UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime); - } + if ((nibble & 4UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime); + } - if ((nibble & 8UL) != 0UL) - { - result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime); - } + if ((nibble & 8UL) != 0UL) + { + result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime); + } - baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime); - remainingExponent >>= 4; - } + baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime); + remainingExponent >>= 4; + } - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } // TODO: Update it with the latest implementations - private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor) - { + private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor) + { ulong modulus = divisor.Modulus; - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } - ulong nPrime = divisor.NPrime; - ulong result = divisor.MontgomeryOne; - ulong baseVal = divisor.MontgomeryTwo; + ulong nPrime = divisor.NPrime; + ulong result = divisor.MontgomeryOne; + ulong baseVal = divisor.MontgomeryTwo; - if (exponent == 0UL) - { - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } + if (exponent == 0UL) + { + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } - const int WindowSize = 5; - Span oddPowers = stackalloc ulong[1 << (WindowSize - 1)]; - oddPowers[0] = baseVal; - ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime); - for (int i = 1; i < oddPowers.Length; i++) - { - oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime); - } + const int WindowSize = 5; + Span oddPowers = stackalloc ulong[1 << (WindowSize - 1)]; + oddPowers[0] = baseVal; + ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime); + for (int i = 1; i < oddPowers.Length; i++) + { + oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime); + } - int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent); + int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent); - while (bitIndex >= 0) - { - if (((exponent >> bitIndex) & 1UL) == 0UL) - { - result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); - bitIndex--; - continue; - } + while (bitIndex >= 0) + { + if (((exponent >> bitIndex) & 1UL) == 0UL) + { + result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); + bitIndex--; + continue; + } - int windowStart = Math.Max(bitIndex - WindowSize + 1, 0); - ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL); + int windowStart = Math.Max(bitIndex - WindowSize + 1, 0); + ulong windowValue = (exponent >> windowStart) & ((1UL << (bitIndex - windowStart + 1)) - 1UL); - while ((windowValue & 1UL) == 0UL) - { - windowValue >>= 1; - windowStart++; - } + while ((windowValue & 1UL) == 0UL) + { + windowValue >>= 1; + windowStart++; + } - int squares = bitIndex - windowStart + 1; - for (int i = 0; i < squares; i++) - { - result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); - } + int squares = bitIndex - windowStart + 1; + for (int i = 0; i < squares; i++) + { + result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); + } - result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime); - bitIndex = windowStart - 1; - } + result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime); + bitIndex = windowStart - 1; + } - return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); - } + return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); + } // TODO: Update it with the latest implementations, if it exists in production code. Otherwise just remove TODO - private static ulong Pow2ModBinary(ulong exponent, ulong modulus) - { - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } + private static ulong Pow2ModBinary(ulong exponent, ulong modulus) + { + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } - ulong result = 1UL % modulus; - ulong baseVal = 2UL % modulus; - ulong remainingExponent = exponent; + ulong result = 1UL % modulus; + ulong baseVal = 2UL % modulus; + ulong remainingExponent = exponent; - while (remainingExponent > 0UL) - { - if ((remainingExponent & 1UL) != 0UL) - { - result = MultiplyMod(result, baseVal, modulus); - } + while (remainingExponent > 0UL) + { + if ((remainingExponent & 1UL) != 0UL) + { + result = MultiplyMod(result, baseVal, modulus); + } - remainingExponent >>= 1; - if (remainingExponent == 0UL) - { - break; - } + remainingExponent >>= 1; + if (remainingExponent == 0UL) + { + break; + } - baseVal = MultiplyMod(baseVal, baseVal, modulus); - } + baseVal = MultiplyMod(baseVal, baseVal, modulus); + } - return result; - } + return result; + } - private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus) - { - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } + private static ulong Pow2ModPrecomputedTable(ulong exponent, ulong modulus) + { + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } - if (exponent == 0UL) - { - return 1UL % modulus; - } + if (exponent == 0UL) + { + return 1UL % modulus; + } - Span powers = stackalloc ulong[64]; - powers[0] = 2UL % modulus; - int highestBit = 63 - BitOperations.LeadingZeroCount(exponent); + Span powers = stackalloc ulong[64]; + powers[0] = 2UL % modulus; + int highestBit = 63 - BitOperations.LeadingZeroCount(exponent); - for (int index = 1; index <= highestBit; index++) - { - powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus); - } + for (int index = 1; index <= highestBit; index++) + { + powers[index] = MultiplyMod(powers[index - 1], powers[index - 1], modulus); + } - ulong result = 1UL % modulus; - ulong remainingExponent = exponent; - int bitIndex = 0; + ulong result = 1UL % modulus; + ulong remainingExponent = exponent; + int bitIndex = 0; - while (remainingExponent > 0UL) - { - if ((remainingExponent & 1UL) != 0UL) - { - result = MultiplyMod(result, powers[bitIndex], modulus); - } + while (remainingExponent > 0UL) + { + if ((remainingExponent & 1UL) != 0UL) + { + result = MultiplyMod(result, powers[bitIndex], modulus); + } - remainingExponent >>= 1; - bitIndex++; - } + remainingExponent >>= 1; + bitIndex++; + } - return result; - } + return result; + } - private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus) - { - if (modulus <= 1UL || (modulus & 1UL) == 0UL) - { - return 0UL; - } + private static ulong Pow2ModBigInteger(ulong exponent, ulong modulus) + { + if (modulus <= 1UL || (modulus & 1UL) == 0UL) + { + return 0UL; + } - BigInteger mod = modulus; - BigInteger result = BigInteger.ModPow(2, exponent, mod); - return (ulong)result; - } + BigInteger mod = modulus; + BigInteger result = BigInteger.ModPow(2, exponent, mod); + return (ulong)result; + } - private static ulong MultiplyMod(ulong x, ulong y, ulong modulus) - { - UInt128 product = (UInt128)x * y; - return (ulong)(product % modulus); - } + private static ulong MultiplyMod(ulong x, ulong y, ulong modulus) + { + UInt128 product = (UInt128)x * y; + return (ulong)(product % modulus); + } } diff --git a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModWindowedBenchmarkData.cs b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModWindowedBenchmarkData.cs index 37a1c18..afb8550 100644 --- a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModWindowedBenchmarkData.cs +++ b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModWindowedBenchmarkData.cs @@ -1,4 +1,5 @@ using PerfectNumbers.Core; +using PerfectNumbers.Core.Gpu.Accelerators; namespace EvenPerfectBitScanner.Benchmarks; @@ -60,12 +61,13 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider }; var cases = new Pow2MontgomeryModWindowedBenchmarkCase[seeds.Length]; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (int i = 0; i < seeds.Length; i++) { Pow2MontgomeryModWindowedBenchmarkSeed seed = seeds[i]; ulong modulus = 2UL * seed.Multiplier * seed.Exponent + 1UL; MontgomeryDivisorData divisor = MontgomeryDivisorDataPool.Shared.FromModulus(modulus); - ulong cycleLength = MersenneDivisorCycles.CalculateCycleLength(modulus, divisor); + ulong cycleLength = MersenneDivisorCycles.CalculateCycleLength(gpu, modulus, divisor); ulong reducedExponent = seed.Exponent % cycleLength; cases[i] = new Pow2MontgomeryModWindowedBenchmarkCase( seed.Exponent, @@ -75,6 +77,7 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider reducedExponent); } + PrimeOrderCalculatorAccelerator.Return(gpu); return new Pow2MontgomeryModWindowedBenchmarkInputs(cases); } diff --git a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks.cs index de961cf..cad2725 100644 --- a/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks.cs @@ -1,6 +1,7 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using PerfectNumbers.Core; +using PerfectNumbers.Core.Gpu.Accelerators; using PerfectNumbers.Core.Gpu.Kernels; namespace EvenPerfectBitScanner.Benchmarks; @@ -31,12 +32,14 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks ulong checksum = 0UL; Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (int i = 0; i < cases.Length; i++) { ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i]; - checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(current.Divisor); + checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(gpu, current.Divisor); } + PrimeOrderCalculatorAccelerator.Return(gpu); return checksum; } @@ -49,12 +52,14 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks ulong checksum = 0UL; Pow2MontgomeryModWindowedBenchmarkCase[] cases = _inputs.Cases; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (int i = 0; i < cases.Length; i++) { ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i]; - checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(current.Divisor); + checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(gpu, current.Divisor); } + PrimeOrderCalculatorAccelerator.Return(gpu); return checksum; } diff --git a/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs index 5dc0110..e786dcc 100644 --- a/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/PrimeTesterBenchmarks.cs @@ -28,15 +28,17 @@ public class PrimeTesterBenchmarks PrimeOrderCalculatorAccelerator.WarmUp(); // HeuristicGroupABPrimeTesterAccelerator.WarmUp(); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + _ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]); - _ = HeuristicPrimeTester.IsPrimeGpu(_candidates[0]); + _ = HeuristicPrimeTester.IsPrimeGpu(gpu, _candidates[0]); _ = Prime.Numbers.IsPrime(_candidates[0]); + PrimeOrderCalculatorAccelerator.Return(gpu); } // [Benchmark] public int HeuristicCpu() { - HeuristicPrimeTester tester = _heuristicTester; ulong[] values = _candidates; int primeCount = 0; @@ -54,17 +56,18 @@ public class PrimeTesterBenchmarks [Benchmark] public int HeuristicGpu() { - HeuristicPrimeTester tester = _heuristicTester; ulong[] values = _candidates; int primeCount = 0; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (int i = 0; i < values.Length; i++) { - if (HeuristicPrimeTester.IsPrimeGpu(values[i])) + if (HeuristicPrimeTester.IsPrimeGpu(gpu, values[i])) { primeCount++; } } + PrimeOrderCalculatorAccelerator.Return(gpu); return primeCount; } @@ -75,9 +78,10 @@ public class PrimeTesterBenchmarks ulong[] values = _candidates; int primeCount = 0; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (int i = 0; i < values.Length; i++) { - if (HeuristicCombinedPrimeTester.IsPrimeGpu(values[i])) + if (HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, values[i])) { primeCount++; } @@ -109,14 +113,16 @@ public class PrimeTesterBenchmarks ulong[] values = _candidates; int primeCount = 0; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (int i = 0; i < values.Length; i++) { - if (PrimeTester.IsPrimeGpu(values[i])) + if (PrimeTester.IsPrimeGpu(gpu, values[i])) { primeCount++; } } + PrimeOrderCalculatorAccelerator.Return(gpu); return primeCount; } diff --git a/EvenPerfectBitScanner.Tests/ProgramTests.cs b/EvenPerfectBitScanner.Tests/ProgramTests.cs index d5f0a27..2007013 100644 --- a/EvenPerfectBitScanner.Tests/ProgramTests.cs +++ b/EvenPerfectBitScanner.Tests/ProgramTests.cs @@ -6,6 +6,7 @@ using PerfectNumbers.Core.Gpu; using EvenPerfectBitScanner.Candidates; using EvenPerfectBitScanner.Candidates.Transforms; using System.Numerics; +using PerfectNumbers.Core.Gpu.Accelerators; namespace EvenPerfectBitScanner.Tests; @@ -104,14 +105,16 @@ public class ProgramTests CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) }); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { - Program.IsEvenPerfectCandidate(11UL, 32UL, out _, out _).Should().BeFalse(); - Program.IsEvenPerfectCandidate(5UL, 32UL, out _, out _).Should().BeTrue(); - Program.IsEvenPerfectCandidate(127UL, 32UL, out _, out _).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, 11UL, 32UL, out _, out _).Should().BeFalse(); + Program.IsEvenPerfectCandidate(gpu, 5UL, 32UL, out _, out _).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, 127UL, 32UL, out _, out _).Should().BeTrue(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); testerField.SetValue(null, null); candidatesField.SetValue(null, null); primeField.SetValue(null, null); @@ -134,14 +137,16 @@ public class ProgramTests CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); candidatesField.SetValue(null, Array.Empty<(ulong, uint)>()); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { - Program.IsEvenPerfectCandidate(11UL, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue(); searched.Should().BeTrue(); detailedCheck.Should().BeFalse(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); testerField.SetValue(null, null); candidatesField.SetValue(null, null); primeField.SetValue(null, null); @@ -163,14 +168,16 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { - Program.IsEvenPerfectCandidate(3UL, 4_000_000_000UL, out bool searched, out bool detailed).Should().BeFalse(); + Program.IsEvenPerfectCandidate(gpu, 3UL, 4_000_000_000UL, out bool searched, out bool detailed).Should().BeFalse(); searched.Should().BeTrue(); detailed.Should().BeTrue(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); testerField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); @@ -191,18 +198,20 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { - Program.IsEvenPerfectCandidate(11UL, 0UL, out bool searched, out bool detailed).Should().BeFalse(); + Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out bool searched, out bool detailed).Should().BeFalse(); searched.Should().BeTrue(); detailed.Should().BeTrue(); - Program.IsEvenPerfectCandidate(13UL, 0UL, out searched, out detailed).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, 13UL, 0UL, out searched, out detailed).Should().BeTrue(); searched.Should().BeTrue(); detailed.Should().BeFalse(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); testerField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); @@ -222,18 +231,20 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { - Program.IsEvenPerfectCandidate(29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse(); + Program.IsEvenPerfectCandidate(gpu, 29UL, 0UL, out bool searched, out bool detailed).Should().BeFalse(); searched.Should().BeTrue(); detailed.Should().BeFalse(); - Program.IsEvenPerfectCandidate(31UL, 0UL, out searched, out detailed).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, 31UL, 0UL, out searched, out detailed).Should().BeTrue(); searched.Should().BeTrue(); detailed.Should().BeTrue(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); mersenneField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); @@ -256,18 +267,20 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { ulong[] primes = [107UL, 127UL]; foreach (ulong prime in primes) { - Program.IsEvenPerfectCandidate(prime, 1024UL, out bool searched, out bool detailed).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, prime, 1024UL, out bool searched, out bool detailed).Should().BeTrue(); searched.Should().BeTrue(); detailed.Should().BeTrue(); } } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); mersenneField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); @@ -291,18 +304,20 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { ulong[] primes = [107UL, 127UL, 521UL, 607UL]; Parallel.ForEach(primes, prime => { - Program.IsEvenPerfectCandidate(prime, 0UL, out bool searched, out bool detailed).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, prime, 0UL, out bool searched, out bool detailed).Should().BeTrue(); searched.Should().BeTrue(); detailed.Should().BeTrue(); }); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); mersenneField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); @@ -325,18 +340,20 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { - Program.IsEvenPerfectCandidate(107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, 107UL, 0UL, out bool searched, out bool detailed).Should().BeTrue(); searched.Should().BeTrue(); detailed.Should().BeTrue(); - Program.IsEvenPerfectCandidate(127UL, 0UL, out searched, out detailed).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, 127UL, 0UL, out searched, out detailed).Should().BeTrue(); searched.Should().BeTrue(); detailed.Should().BeTrue(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); mersenneField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); @@ -362,13 +379,15 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { - Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue(); searched.Should().BeTrue(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); mersenneField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); @@ -397,13 +416,15 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { - Program.IsEvenPerfectCandidate(exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue(); + Program.IsEvenPerfectCandidate(gpu, exponent, 0UL, out bool searched, out bool detailedCheck).Should().BeTrue(); searched.Should().BeTrue(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); mersenneField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); @@ -476,21 +497,23 @@ public class ProgramTests primeField.SetValue(null, new ThreadLocal(() => new PrimeTester(), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try { compositeField.SetValue(null, false); - Program.IsEvenPerfectCandidate(9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse(); + Program.IsEvenPerfectCandidate(gpu, 9UL, 0UL, out bool searched, out bool detailed).Should().BeFalse(); searched.Should().BeFalse(); detailed.Should().BeFalse(); ((bool)compositeField.GetValue(null)!).Should().BeTrue(); - Program.IsEvenPerfectCandidate(11UL, 0UL, out searched, out detailed); + Program.IsEvenPerfectCandidate(gpu, 11UL, 0UL, out searched, out detailed); searched.Should().BeTrue(); ((bool)compositeField.GetValue(null)!).Should().BeFalse(); } finally { + PrimeOrderCalculatorAccelerator.Return(gpu); mersenneField.SetValue(null, null); primeField.SetValue(null, null); CandidatesCalculator.ResetResidueTrackers(); diff --git a/EvenPerfectBitScanner/Candidates/CandidatesCalculator.cs b/EvenPerfectBitScanner/Candidates/CandidatesCalculator.cs index 676e6f8..d6eac5c 100644 --- a/EvenPerfectBitScanner/Candidates/CandidatesCalculator.cs +++ b/EvenPerfectBitScanner/Candidates/CandidatesCalculator.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using Open.Numeric.Primes; using PerfectNumbers.Core; using EvenPerfectBitScanner.Candidates.Transforms; +using PerfectNumbers.Core.Gpu.Accelerators; namespace EvenPerfectBitScanner.Candidates; @@ -53,7 +54,7 @@ internal static class CandidatesCalculator return trackers.Value!; } - internal static bool IsCompositeByResidues(ulong p) + internal static bool IsCompositeByResidues(PrimeOrderCalculatorAccelerator gpu, ulong p) { ModResidueTracker tracker = AcquireResidueTracker(); // Use ModResidueTracker with a small set of primes to pre-filter composite p. @@ -71,7 +72,7 @@ internal static class CandidatesCalculator break; } - if (tracker.MergeOrAppend(p, primes[primeIndex], out bool divisible) && divisible) + if (tracker.MergeOrAppend(gpu, p, primes[primeIndex], out bool divisible) && divisible) { return true; } diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index a5610d1..689a820 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -7,6 +7,7 @@ using Open.Collections; using PerfectNumbers.Core; using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; namespace EvenPerfectBitScanner; @@ -64,7 +65,7 @@ internal static class Program PrimeTester.GpuBatchSize = gpuPrimeBatch; GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads); Console.WriteLine("Warming up GPU kernels"); - PrimeTester.WarmUpGpuKernels(1024); + PrimeTester.WarmUpGpuKernels(1024); // PrimeTester.WarmUpGpuKernels(gpuPrimeThreads >> 4); Console.WriteLine("Starting up threads..."); _ = UnboundedTaskScheduler.Instance; @@ -425,7 +426,7 @@ internal static class Program static (candidate, searchedMersenne, detailedCheck, passedAllTests, divisor) => CalculationResultHandler.HandleResult(candidate, divisor, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP), threadCount, byDivisorPrimeRange); - + CalculationResultHandler.FlushBuffer(); CalculationResultHandler.ReleaseOutputBuffer(); if (stopwatch is not null) @@ -469,6 +470,7 @@ internal static class Program bool passedAllTests, searchedMersenne, detailedCheck; ArrayPool pool = ThreadStaticPools.UlongPool; ulong[] buffer = pool.Rent(blockSize); + PrimeOrderCalculatorAccelerator gpu = PrimeOrderCalculatorAccelerator.Rent(1); while (!Volatile.Read(ref _limitReached)) { count = CandidatesCalculator.ReserveBlock(buffer, blockSize, ref _limitReached); @@ -482,7 +484,7 @@ internal static class Program for (j = 0; j < count && !Volatile.Read(ref _limitReached); j++) { p = buffer[j]; - passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); + passedAllTests = IsEvenPerfectCandidate(gpu, p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); } @@ -505,7 +507,7 @@ internal static class Program continue; } - passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); + passedAllTests = IsEvenPerfectCandidate(gpu, p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); if (p == maxP) @@ -522,6 +524,7 @@ internal static class Program } pool.Return(buffer); + PrimeOrderCalculatorAccelerator.Return(gpu); }); } @@ -565,7 +568,7 @@ internal static class Program } } - internal static bool IsEvenPerfectCandidate(ulong p, ulong divisorCyclesSearchLimit, out bool searchedMersenne, out bool detailedCheck) + internal static bool IsEvenPerfectCandidate(PrimeOrderCalculatorAccelerator gpu, ulong p, ulong divisorCyclesSearchLimit, out bool searchedMersenne, out bool detailedCheck) { searchedMersenne = false; detailedCheck = false; @@ -615,7 +618,7 @@ internal static class Program } // Fast residue-based composite check for p using small primes - if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor && CandidatesCalculator.IsCompositeByResidues(p)) + if (!_cliArguments.UseDivisor && !_cliArguments.UseByDivisor && CandidatesCalculator.IsCompositeByResidues(gpu, p)) { searchedMersenne = false; detailedCheck = false; @@ -628,7 +631,7 @@ internal static class Program { if (!(_runPrimesOnCpu ? PrimeTester.IsPrime(p) - : HeuristicCombinedPrimeTester.IsPrimeGpu(p))) + : HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, p))) { _lastCompositeP = true; return false; @@ -641,7 +644,7 @@ internal static class Program if (_cliArguments.UseDivisor) { bool divisorsExhausted; - bool isPrime = _divisorTester!.IsPrime(p, _divisor, divisorCyclesSearchLimit, out divisorsExhausted); + bool isPrime = _divisorTester!.IsPrime(gpu, p, _divisor, divisorCyclesSearchLimit, out divisorsExhausted); detailedCheck = divisorsExhausted; return isPrime; } @@ -649,10 +652,10 @@ internal static class Program if (_cliArguments.UseByDivisor) { // Windowed pow2mod kernel handles by-divisor scans. - return _byDivisorTester!.IsPrime(p, out detailedCheck, out _); + return _byDivisorTester!.IsPrime(gpu, p, out detailedCheck, out _); } - detailedCheck = MersenneTesters.Value!.IsMersennePrime(p); + detailedCheck = MersenneTesters.Value!.IsMersennePrime(gpu, p); return detailedCheck; } } diff --git a/PerfectNumbers.Core.Tests/Gpu/LucasLehmerPrimeTesterTests.cs b/PerfectNumbers.Core.Tests/Gpu/LucasLehmerPrimeTesterTests.cs index 4be4955..bd01b29 100644 --- a/PerfectNumbers.Core.Tests/Gpu/LucasLehmerPrimeTesterTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/LucasLehmerPrimeTesterTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests.Gpu; @@ -7,59 +8,91 @@ namespace PerfectNumbers.Core.Tests.Gpu; [Trait("Category", "Slow")] public class LucasLehmerPrimeTesterTests { - [Theory] - [InlineData(3UL, true)] - [InlineData(5UL, true)] - [InlineData(11UL, false)] - [Trait("Category", "Fast")] - public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected) - { - new MersenneNumberLucasLehmerGpuTester().IsPrime(p, true).Should().Be(expected); - } + [Theory] + [InlineData(3UL, true)] + [InlineData(5UL, true)] + [InlineData(11UL, false)] + [Trait("Category", "Fast")] + public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected) + { + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + new MersenneNumberLucasLehmerGpuTester().IsPrime(gpu, p, true).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback() - { - // Use a modest exponent that still exercises the NTT path while keeping - // runtime short enough for the unit test environment. - const ulong exponent = 128UL; - var tester = new MersenneNumberLucasLehmerGpuTester(); - tester.WarmUpNttParameters(exponent); - tester.IsPrime(exponent, true).Should().BeFalse(); - } + [Fact] + [Trait("Category", "Fast")] + public void IsMersennePrime_gpu_large_exponent_without_cpu_fallback() + { + // Use a modest exponent that still exercises the NTT path while keeping + // runtime short enough for the unit test environment. + const ulong exponent = 128UL; + var tester = new MersenneNumberLucasLehmerGpuTester(); + tester.WarmUpNttParameters(exponent); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, exponent, true).Should().BeFalse(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void IsMersennePrimeBatch_matches_individual() - { - ulong[] exponents = new ulong[] { 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 19UL, 23UL }; - bool[] expected = new bool[] { true, true, true, false, true, true, true, false }; - var tester = new MersenneNumberLucasLehmerGpuTester(); - bool[] results = new bool[exponents.Length]; - tester.IsMersennePrimeBatch(exponents, results); - results.Should().Equal(expected); - } + [Fact] + [Trait("Category", "Fast")] + public void IsMersennePrimeBatch_matches_individual() + { + ulong[] exponents = new ulong[] { 3UL, 5UL, 7UL, 11UL, 13UL, 17UL, 19UL, 23UL }; + bool[] expected = new bool[] { true, true, true, false, true, true, true, false }; + var tester = new MersenneNumberLucasLehmerGpuTester(); + bool[] results = new bool[exponents.Length]; + tester.IsMersennePrimeBatch(exponents, results); + results.Should().Equal(expected); + } - [Theory] - [InlineData(132UL)] - [InlineData(1000UL)] - [Trait("Category", "Slow")] - public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent) - { - var tester = new MersenneNumberLucasLehmerGpuTester(); - tester.WarmUpNttParameters(exponent); - tester.IsPrime(exponent, true).Should().BeFalse(); - } + [Theory] + [InlineData(132UL)] + [InlineData(1000UL)] + [Trait("Category", "Slow")] + public void WarmUpNttParameters_populates_cache_for_large_exponent(ulong exponent) + { + var tester = new MersenneNumberLucasLehmerGpuTester(); + tester.WarmUpNttParameters(exponent); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, exponent, true).Should().BeFalse(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Theory] - [InlineData(3UL, true)] - [InlineData(5UL, true)] - [InlineData(11UL, false)] - [Trait("Category", "Fast")] - public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected) - { - new MersenneNumberLucasLehmerCpuTester().IsPrime(p).Should().Be(expected); - } + [Theory] + [InlineData(3UL, true)] + [InlineData(5UL, true)] + [InlineData(11UL, false)] + [Trait("Category", "Fast")] + public void IsMersennePrime_cpu_matches_expected_results(ulong p, bool expected) + { + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + new MersenneNumberLucasLehmerCpuTester().IsPrime(gpu, p).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/Gpu/PrimeOrderGpuHeuristicsTests.cs b/PerfectNumbers.Core.Tests/Gpu/PrimeOrderGpuHeuristicsTests.cs index 5ac6ec4..9aa5340 100644 --- a/PerfectNumbers.Core.Tests/Gpu/PrimeOrderGpuHeuristicsTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/PrimeOrderGpuHeuristicsTests.cs @@ -2,6 +2,7 @@ using System.Numerics; using System.Reflection; using FluentAssertions; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; @@ -9,367 +10,383 @@ namespace PerfectNumbers.Core.Tests; [Trait("Category", "Fast")] public class PrimeOrderGpuHeuristicsTests { - public PrimeOrderGpuHeuristicsTests() - { - PrimeOrderGpuHeuristics.OverflowRegistry.Clear(); - PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear(); - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - } + public PrimeOrderGpuHeuristicsTests() + { + PrimeOrderGpuHeuristics.OverflowRegistry.Clear(); + PrimeOrderGpuHeuristics.OverflowRegistryWide.Clear(); + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + } - [Fact] - public void TryPow2Mod_returns_overflow_for_marked_prime() - { - const ulong prime = 97UL; - try - { - PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData); + [Fact] + public void TryPow2Mod_returns_overflow_for_marked_prime() + { + const ulong prime = 97UL; + try + { + PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0; + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainder.Should().Be(0UL); - } - finally - { - PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainder.Should().Be(0UL); + } + finally + { + PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); + } + } - [Fact] - public void TryPow2Mod_returns_success_for_supported_prime() - { - const ulong exponent = 138_000_001UL; - const ulong prime = 138_000_001UL; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData); + [Fact] + public void TryPow2Mod_returns_success_for_supported_prime() + { + const ulong exponent = 138_000_001UL; + const ulong prime = 138_000_001UL; + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData); - status.Should().Be(GpuPow2ModStatus.Success); - remainder.Should().Be(2UL); - } + status.Should().Be(GpuPow2ModStatus.Success); + remainder.Should().Be(2UL); + } - [Fact] - public void TryPow2Mod_returns_overflow_when_modulus_exceeds_capability() - { - const ulong prime = 97UL; - var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64); - PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); - try - { - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData); + [Fact] + public void TryPow2Mod_returns_overflow_when_modulus_exceeds_capability() + { + const ulong prime = 97UL; + var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64); + PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); + try + { + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainder.Should().Be(0UL); - } - finally - { - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainder.Should().Be(0UL); + } + finally + { + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); + } + } - [Fact] - public void TryPow2Mod_returns_overflow_when_exponent_exceeds_capability() - { - var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3); - PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); - try - { - const ulong prime = 193UL; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, prime, out ulong remainder, divisorData); + [Fact] + public void TryPow2Mod_returns_overflow_when_exponent_exceeds_capability() + { + var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3); + PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); + try + { + const ulong prime = 193UL; + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, prime, out ulong remainder, divisorData); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainder.Should().Be(0UL); + status.Should().Be(GpuPow2ModStatus.Overflow); + remainder.Should().Be(0UL); - GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, prime, out ulong followUpRemainder, divisorData); - followUp.Should().Be(GpuPow2ModStatus.Success); - followUpRemainder.Should().Be(16UL); - } - finally - { - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - } - } + GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, prime, out ulong followUpRemainder, divisorData); + followUp.Should().Be(GpuPow2ModStatus.Success); + followUpRemainder.Should().Be(16UL); + } + finally + { + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + } + } - [Fact] - public void TryPow2ModBatch_returns_overflow_for_marked_prime() - { - const ulong prime = 193UL; - try - { - PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0; + [Fact] + public void TryPow2ModBatch_returns_overflow_for_marked_prime() + { + const ulong prime = 193UL; + try + { + PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0; - ulong[] remainders = new ulong[2]; - ulong[] exponents = { 1UL, 2UL }; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); + ulong[] remainders = new ulong[2]; + ulong[] exponents = { 1UL, 2UL }; + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainders[0].Should().Be(0UL); - remainders[1].Should().Be(0UL); - } - finally - { - PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainders[0].Should().Be(0UL); + remainders[1].Should().Be(0UL); + } + finally + { + PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); + } + } - [Fact] - public void TryPow2ModBatch_returns_success_for_supported_prime() - { - ulong[] remainders = { 123UL, 456UL }; - ulong[] exponents = { 138_000_001UL, 138_000_011UL }; - const ulong prime = 138_000_001UL; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); + [Fact] + public void TryPow2ModBatch_returns_success_for_supported_prime() + { + ulong[] remainders = { 123UL, 456UL }; + ulong[] exponents = { 138_000_001UL, 138_000_011UL }; + const ulong prime = 138_000_001UL; + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); - ulong expectedFirst = (ulong)BigInteger.ModPow(2, exponents[0], prime); - ulong expectedSecond = (ulong)BigInteger.ModPow(2, exponents[1], prime); + ulong expectedFirst = (ulong)BigInteger.ModPow(2, exponents[0], prime); + ulong expectedSecond = (ulong)BigInteger.ModPow(2, exponents[1], prime); - status.Should().Be(GpuPow2ModStatus.Success); - expectedFirst.Should().Be(2UL); - expectedSecond.Should().Be(2048UL); - remainders[0].Should().Be(expectedFirst); - remainders[1].Should().Be(expectedSecond); - } + status.Should().Be(GpuPow2ModStatus.Success); + expectedFirst.Should().Be(2UL); + expectedSecond.Should().Be(2048UL); + remainders[0].Should().Be(expectedFirst); + remainders[1].Should().Be(expectedSecond); + } - [Fact] - public void TryPow2ModBatch_returns_overflow_when_modulus_exceeds_capability() - { - const ulong prime = 97UL; - var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64); - PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); - try - { - ulong[] exponents = { 1UL, 2UL }; - ulong[] remainders = { 5UL, 6UL }; + [Fact] + public void TryPow2ModBatch_returns_overflow_when_modulus_exceeds_capability() + { + const ulong prime = 97UL; + var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(6, 64); + PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); + try + { + ulong[] exponents = { 1UL, 2UL }; + ulong[] remainders = { 5UL, 6UL }; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainders[0].Should().Be(0UL); - remainders[1].Should().Be(0UL); - } - finally - { - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainders[0].Should().Be(0UL); + remainders[1].Should().Be(0UL); + } + finally + { + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); + } + } - [Fact] - public void TryPow2ModBatch_returns_overflow_when_any_exponent_exceeds_capability() - { - var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3); - PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); - try - { - ulong[] exponents = { 2UL, 16UL }; - ulong[] remainders = { 9UL, 11UL }; + [Fact] + public void TryPow2ModBatch_returns_overflow_when_any_exponent_exceeds_capability() + { + var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 3); + PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); + try + { + ulong[] exponents = { 2UL, 16UL }; + ulong[] remainders = { 9UL, 11UL }; - const ulong prime = 193UL; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); + const ulong prime = 193UL; + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainders[0].Should().Be(0UL); - remainders[1].Should().Be(0UL); - } - finally - { - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainders[0].Should().Be(0UL); + remainders[1].Should().Be(0UL); + } + finally + { + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + } + } - [Fact] - public void TryPow2ModWide_returns_overflow_for_marked_prime() - { - UInt128 prime = ((UInt128)1 << 80) + 7; - try - { - PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0; + [Fact] + public void TryPow2ModWide_returns_overflow_for_marked_prime() + { + UInt128 prime = ((UInt128)1 << 80) + 7; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + PrimeOrderGpuHeuristics.OverflowRegistryWide[prime] = 0; - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, UInt128.One, prime, out UInt128 remainder); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainder.Should().Be(UInt128.Zero); - } - finally - { - PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainder.Should().Be(UInt128.Zero); + } + finally + { + PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - public void TryPow2ModWide_returns_success_for_supported_prime() - { - UInt128 prime = ((UInt128)1 << 96) + 11; - UInt128 exponent = ((UInt128)1 << 64) + 3; + [Fact] + public void TryPow2ModWide_returns_success_for_supported_prime() + { + UInt128 prime = ((UInt128)1 << 96) + 11; + UInt128 exponent = ((UInt128)1 << 64) + 3; - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out UInt128 remainder); + PrimeOrderCalculatorAccelerator.Return(gpu); - status.Should().Be(GpuPow2ModStatus.Success); - UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime); - remainder.Should().Be(expected); - } + status.Should().Be(GpuPow2ModStatus.Success); + UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponent, (BigInteger)prime); + remainder.Should().Be(expected); + } - [Theory] - [InlineData("193", "31")] - [InlineData("193", "255")] - [InlineData("79228162514264337593543950347", "36893488147419103251")] - public void Pow2ModKernelCore_matches_cpu_windowed_path(string modulusText, string exponentText) - { - UInt128 modulus = UInt128.Parse(modulusText); - UInt128 exponent = UInt128.Parse(exponentText); + [Theory] + [InlineData("193", "31")] + [InlineData("193", "255")] + [InlineData("79228162514264337593543950347", "36893488147419103251")] + public void Pow2ModKernelCore_matches_cpu_windowed_path(string modulusText, string exponentText) + { + UInt128 modulus = UInt128.Parse(modulusText); + UInt128 exponent = UInt128.Parse(exponentText); - var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static); - method.Should().NotBeNull(); + var method = typeof(PrimeOrderGpuHeuristics).GetMethod("Pow2ModKernelCore", BindingFlags.NonPublic | BindingFlags.Static); + method.Should().NotBeNull(); - object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) }); - GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!; - UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus); + object? gpuResultObject = method!.Invoke(null, new object[] { new GpuUInt128(exponent), new GpuUInt128(modulus) }); + GpuUInt128 gpuResult = (GpuUInt128)gpuResultObject!; + UInt128 expected = exponent.Pow2MontgomeryModWindowed(modulus); - ((UInt128)gpuResult).Should().Be(expected); - } + ((UInt128)gpuResult).Should().Be(expected); + } - [Fact] - public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability() - { - UInt128 prime = ((UInt128)1 << 80) + 9; - var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128); - PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); - try - { - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(UInt128.One, prime, out UInt128 remainder); + [Fact] + public void TryPow2ModWide_returns_overflow_when_modulus_exceeds_capability() + { + UInt128 prime = ((UInt128)1 << 80) + 9; + var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128); + PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, UInt128.One, prime, out UInt128 remainder); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainder.Should().Be(UInt128.Zero); - } - finally - { - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainder.Should().Be(UInt128.Zero); + } + finally + { + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability() - { - var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4); - PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); - try - { - UInt128 exponent = UInt128.One << 8; - UInt128 prime = ((UInt128)1 << 70) + 3; + [Fact] + public void TryPow2ModWide_returns_overflow_when_exponent_exceeds_capability() + { + var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 4); + PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + UInt128 exponent = UInt128.One << 8; + UInt128 prime = ((UInt128)1 << 70) + 3; - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out UInt128 remainder); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, prime, out UInt128 remainder); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainder.Should().Be(UInt128.Zero); - } - finally - { - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainder.Should().Be(UInt128.Zero); + } + finally + { + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - public void TryPow2ModBatchWide_returns_success_for_supported_prime() - { - UInt128 prime = ((UInt128)1 << 90) + 19; - UInt128[] exponents = { UInt128.One, ((UInt128)1 << 64) + 5 }; - UInt128[] remainders = { UInt128.Zero, UInt128.Zero }; + [Fact] + public void TryPow2ModBatchWide_returns_success_for_supported_prime() + { + UInt128 prime = ((UInt128)1 << 90) + 19; + UInt128[] exponents = { UInt128.One, ((UInt128)1 << 64) + 5 }; + UInt128[] remainders = { UInt128.Zero, UInt128.Zero }; - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders); + PrimeOrderCalculatorAccelerator.Return(gpu); - status.Should().Be(GpuPow2ModStatus.Success); - for (int i = 0; i < exponents.Length; i++) - { - UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime); - remainders[i].Should().Be(expected); - } - } + status.Should().Be(GpuPow2ModStatus.Success); + for (int i = 0; i < exponents.Length; i++) + { + UInt128 expected = (UInt128)BigInteger.ModPow(2, (BigInteger)exponents[i], (BigInteger)prime); + remainders[i].Should().Be(expected); + } + } - [Fact] - public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability() - { - UInt128 prime = ((UInt128)1 << 88) + 13; - UInt128[] exponents = { UInt128.One, (UInt128)5 }; - UInt128[] remainders = { UInt128.Zero, UInt128.Zero }; - var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128); - PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); - try - { - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders); + [Fact] + public void TryPow2ModBatchWide_returns_overflow_when_modulus_exceeds_capability() + { + UInt128 prime = ((UInt128)1 << 88) + 13; + UInt128[] exponents = { UInt128.One, (UInt128)5 }; + UInt128[] remainders = { UInt128.Zero, UInt128.Zero }; + var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(64, 128); + PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainders[0].Should().Be(UInt128.Zero); - remainders[1].Should().Be(UInt128.Zero); - } - finally - { - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainders[0].Should().Be(UInt128.Zero); + remainders[1].Should().Be(UInt128.Zero); + } + finally + { + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + PrimeOrderGpuHeuristics.OverflowRegistryWide.TryRemove(prime, out _); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability() - { - UInt128 prime = ((UInt128)1 << 72) + 23; - UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 }; - UInt128[] remainders = { UInt128.Zero, UInt128.Zero }; - var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6); - PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); - try - { - GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders); + [Fact] + public void TryPow2ModBatchWide_returns_overflow_when_any_exponent_exceeds_capability() + { + UInt128 prime = ((UInt128)1 << 72) + 23; + UInt128[] exponents = { UInt128.One << 5, UInt128.One << 9 }; + UInt128[] remainders = { UInt128.Zero, UInt128.Zero }; + var capability = new PrimeOrderGpuHeuristics.PrimeOrderGpuCapability(128, 6); + PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(gpu, exponents, prime, remainders); - status.Should().Be(GpuPow2ModStatus.Overflow); - remainders[0].Should().Be(UInt128.Zero); - remainders[1].Should().Be(UInt128.Zero); - } - finally - { - PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); - } - } + status.Should().Be(GpuPow2ModStatus.Overflow); + remainders[0].Should().Be(UInt128.Zero); + remainders[1].Should().Be(UInt128.Zero); + } + finally + { + PrimeOrderGpuHeuristics.ResetCapabilitiesForTesting(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - // This will never happen in production code because the caller ensures the remainder span is large enough. - // [Fact] - // public void TryPow2ModBatch_throws_when_remainder_span_is_too_small() - // { - // ulong[] remainders = new ulong[1]; - // ulong[] exponents = { 1UL, 2UL }; + // This will never happen in production code because the caller ensures the remainder span is large enough. + // [Fact] + // public void TryPow2ModBatch_throws_when_remainder_span_is_too_small() + // { + // ulong[] remainders = new ulong[1]; + // ulong[] exponents = { 1UL, 2UL }; - // Action act = () => - // { - // const ulong prime = 5UL; - // MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime); - // PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); - // }; + // Action act = () => + // { + // const ulong prime = 5UL; + // MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime); + // PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData); + // }; - // act.Should().Throw(); - // } + // act.Should().Throw(); + // } - [Fact] - public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked() - { - const ulong prime = 7UL; - try - { - PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0; + [Fact] + public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked() + { + const ulong prime = 7UL; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); + ulong result = PrimeOrderCalculator.Calculate(gpu, prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - result.Should().Be(3UL); - } - finally - { - PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); - } - } + result.Should().Be(3UL); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + PrimeOrderGpuHeuristics.OverflowRegistry.TryRemove(prime, out _); + } + } } diff --git a/PerfectNumbers.Core.Tests/Gpu/PrimeTesterGpuTests.cs b/PerfectNumbers.Core.Tests/Gpu/PrimeTesterGpuTests.cs index 614a91c..33a43b4 100644 --- a/PerfectNumbers.Core.Tests/Gpu/PrimeTesterGpuTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/PrimeTesterGpuTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests.Gpu; @@ -6,26 +7,39 @@ namespace PerfectNumbers.Core.Tests.Gpu; [Trait("Category", "Fast")] public class PrimeTesterGpuTests { - [Fact] - public void IsPrimeGpu_accepts_known_primes() - { - var tester = new PrimeTester(); + [Fact] + public void IsPrimeGpu_accepts_known_primes() + { ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL]; - foreach (ulong prime in primes) + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try { - PrimeTester.IsPrimeGpu(prime).Should().BeTrue(); + foreach (ulong prime in primes) + { + PrimeTester.IsPrimeGpu(gpu, prime).Should().BeTrue(); + } } - } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - public void IsPrimeGpu_rejects_composites() - { - var tester = new PrimeTester(); + [Fact] + public void IsPrimeGpu_rejects_composites() + { ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL]; - foreach (ulong composite in composites) + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try { - PrimeTester.IsPrimeGpu(composite).Should().BeFalse(); + foreach (ulong composite in composites) + { + PrimeTester.IsPrimeGpu(gpu, composite).Should().BeFalse(); + } } - } - + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneDivisorCyclesGenerateGpuTests.cs b/PerfectNumbers.Core.Tests/MersenneDivisorCyclesGenerateGpuTests.cs index 58a2967..ff73525 100644 --- a/PerfectNumbers.Core.Tests/MersenneDivisorCyclesGenerateGpuTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneDivisorCyclesGenerateGpuTests.cs @@ -1,107 +1,112 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; public class MersenneDivisorCyclesGenerateGpuTests { - [Fact] - [Trait("Category", "Fast")] - public void GenerateGpu_produces_expected_cycles_for_small_range() - { - string path = Path.GetTempFileName(); - try - { - MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0); - var pairs = new List<(ulong divisor, ulong cycle)>(); - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); - using var reader = new BinaryReader(stream); - while (stream.Position < stream.Length) - { - ulong d = reader.ReadUInt64(); - ulong c = reader.ReadUInt64(); - pairs.Add((d, c)); - } + [Fact] + [Trait("Category", "Fast")] + public void GenerateGpu_produces_expected_cycles_for_small_range() + { + string path = Path.GetTempFileName(); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0); + var pairs = new List<(ulong divisor, ulong cycle)>(); + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var reader = new BinaryReader(stream); + while (stream.Position < stream.Length) + { + ulong d = reader.ReadUInt64(); + ulong c = reader.ReadUInt64(); + pairs.Add((d, c)); + } - var expected = new List(); - for (ulong d = 3; d <= 50; d++) - { - if ((d & 1UL) == 0UL || d % 3UL == 0UL || d % 5UL == 0UL || d % 7UL == 0UL || d % 11UL == 0UL) - { - continue; - } + var expected = new List(); + for (ulong d = 3; d <= 50; d++) + { + if ((d & 1UL) == 0UL || d % 3UL == 0UL || d % 5UL == 0UL || d % 7UL == 0UL || d % 11UL == 0UL) + { + continue; + } - expected.Add(d); - } + expected.Add(d); + } - pairs.Should().HaveCount(expected.Count); - foreach (var pair in pairs) - { - expected.Should().Contain(pair.divisor); - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor); - pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData)); - } - } - finally - { - if (File.Exists(path)) - { - File.Delete(path); - } - } - } + pairs.Should().HaveCount(expected.Count); + foreach (var pair in pairs) + { + expected.Should().Contain(pair.divisor); + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor); + pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(gpu, pair.divisor, divisorData)); + } + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + if (File.Exists(path)) + { + File.Delete(path); + } + } + } - [Fact] - [Trait("Category", "Fast")] - public void Generate_produces_unique_cycles_for_expected_divisors() - { - string path = Path.GetTempFileName(); - try - { - MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4); + [Fact] + [Trait("Category", "Fast")] + public void Generate_produces_unique_cycles_for_expected_divisors() + { + string path = Path.GetTempFileName(); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4); - var pairs = new List<(ulong divisor, ulong cycle)>(); - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); - using var reader = new BinaryReader(stream); - while (stream.Position < stream.Length) - { - ulong divisor = reader.ReadUInt64(); - ulong cycle = reader.ReadUInt64(); - pairs.Add((divisor, cycle)); - } + var pairs = new List<(ulong divisor, ulong cycle)>(); + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var reader = new BinaryReader(stream); + while (stream.Position < stream.Length) + { + ulong divisor = reader.ReadUInt64(); + ulong cycle = reader.ReadUInt64(); + pairs.Add((divisor, cycle)); + } - var expected = new List(); - for (ulong divisor = 3; divisor <= 50; divisor++) - { - if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL) - { - continue; - } + var expected = new List(); + for (ulong divisor = 3; divisor <= 50; divisor++) + { + if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL) + { + continue; + } - expected.Add(divisor); - } + expected.Add(divisor); + } - pairs.Should().HaveCount(expected.Count); + pairs.Should().HaveCount(expected.Count); - HashSet uniqueDivisors = new(); - foreach (var pair in pairs) - { - uniqueDivisors.Add(pair.divisor).Should().BeTrue(); - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor); - pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData)); - } + HashSet uniqueDivisors = new(); + foreach (var pair in pairs) + { + uniqueDivisors.Add(pair.divisor).Should().BeTrue(); + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(pair.divisor); + pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(gpu, pair.divisor, divisorData)); + } - uniqueDivisors.Should().BeEquivalentTo(expected); + uniqueDivisors.Should().BeEquivalentTo(expected); - var (_, completeCount) = MersenneDivisorCycles.FindLast(path); - completeCount.Should().Be(expected.Count); - } - finally - { - if (File.Exists(path)) - { - File.Delete(path); - } - } - } + var (_, completeCount) = MersenneDivisorCycles.FindLast(path); + completeCount.Should().Be(expected.Count); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + if (File.Exists(path)) + { + File.Delete(path); + } + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneDivisorCyclesTests.cs b/PerfectNumbers.Core.Tests/MersenneDivisorCyclesTests.cs index a332fdc..29e5964 100644 --- a/PerfectNumbers.Core.Tests/MersenneDivisorCyclesTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneDivisorCyclesTests.cs @@ -1,26 +1,35 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; public class MersenneDivisorCyclesTests { - public static IEnumerable Divisors() => - new[] - { - new object[] {3UL, 2UL}, - new object[] {7UL, 3UL}, - new object[] {11UL, 10UL}, - }; + public static IEnumerable Divisors() => + new[] + { + new object[] {3UL, 2UL}, + new object[] {7UL, 3UL}, + new object[] {11UL, 10UL}, + }; - [Theory] - [Trait("Category", "Fast")] - [MemberData(nameof(Divisors))] - public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected) - { - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(divisor); - MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData).Should().Be(expected); - MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected); - } + [Theory] + [Trait("Category", "Fast")] + [MemberData(nameof(Divisors))] + public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected) + { + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(divisor); + MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData).Should().Be(expected); + MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs index b360c46..fe78197 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorByDivisorTesterTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; @@ -57,7 +58,7 @@ public class MersenneNumberDivisorByDivisorTesterTests return DivisorLimit; } - public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) + public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor) { divisorsExhausted = true; if (prime % 7UL == 0UL) @@ -76,7 +77,7 @@ public class MersenneNumberDivisorByDivisorTesterTests return true; } - public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() + public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu) { return new DummySession(); } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs index 5581905..0006bc6 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs @@ -1,107 +1,122 @@ using FluentAssertions; using PerfectNumbers.Core.Cpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; public class MersenneNumberDivisorCpuTesterTests { - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_tester_tracks_divisors_across_primes() - { - var tester = new MersenneNumberDivisorByDivisorCpuTester(); - tester.ConfigureFromMaxPrime(11UL); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_tester_tracks_divisors_across_primes() + { + var tester = new MersenneNumberDivisorByDivisorCpuTester(); + tester.ConfigureFromMaxPrime(11UL); - tester.IsPrime(5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); - divisorsExhausted.Should().BeTrue(); - divisor.Should().Be(0UL); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, 5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); + divisorsExhausted.Should().BeTrue(); + divisor.Should().Be(0UL); - tester.IsPrime(7UL, out divisorsExhausted, out divisor).Should().BeTrue(); - divisorsExhausted.Should().BeTrue(); - divisor.Should().Be(0UL); + tester.IsPrime(gpu, 7UL, out divisorsExhausted, out divisor).Should().BeTrue(); + divisorsExhausted.Should().BeTrue(); + divisor.Should().Be(0UL); - tester.IsPrime(11UL, out divisorsExhausted, out divisor).Should().BeFalse(); - divisorsExhausted.Should().BeTrue(); - divisor.Should().BeGreaterThan(0UL); - } + tester.IsPrime(gpu, 11UL, out divisorsExhausted, out divisor).Should().BeFalse(); + divisorsExhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_session_checks_divisors_across_primes() - { - var tester = new MersenneNumberDivisorByDivisorCpuTester(); - tester.ConfigureFromMaxPrime(13UL); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_session_checks_divisors_across_primes() + { + var tester = new MersenneNumberDivisorByDivisorCpuTester(); + tester.ConfigureFromMaxPrime(13UL); - var session = tester.CreateDivisorSession(); - try - { - ulong[] primes = { 5UL, 7UL, 11UL, 13UL }; - byte[] hits = new byte[primes.Length]; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] primes = { 5UL, 7UL, 11UL, 13UL }; + byte[] hits = new byte[primes.Length]; - ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL)); - session.CheckDivisor(23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL), cycle23, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); + ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(gpu, 23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL)); + session.CheckDivisor(23UL, MontgomeryDivisorDataPool.Shared.FromModulus(23UL), cycle23, primes, hits); + hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); - Array.Fill(hits, (byte)0); - ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL)); - session.CheckDivisor(31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL), cycle31, primes, hits); - hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 }); - } - finally - { - session.Return(); - } - } + Array.Fill(hits, (byte)0); + ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(gpu, 31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL)); + session.CheckDivisor(31UL, MontgomeryDivisorDataPool.Shared.FromModulus(31UL), cycle31, primes, hits); + hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite() - { - var tester = new MersenneNumberDivisorByDivisorCpuTester(); - tester.ConfigureFromMaxPrime(43UL); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite() + { + var tester = new MersenneNumberDivisorByDivisorCpuTester(); + tester.ConfigureFromMaxPrime(43UL); - var session = tester.CreateDivisorSession(); - try - { - ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; - byte[] hits = new byte[exponents.Length]; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; + byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData); - session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 7UL, divisorData); + session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); - } - finally - { - session.Return(); - } - } + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite() - { - var tester = new MersenneNumberDivisorByDivisorCpuTester(); - tester.ConfigureFromMaxPrime(61UL); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite() + { + var tester = new MersenneNumberDivisorByDivisorCpuTester(); + tester.ConfigureFromMaxPrime(61UL); - var session = tester.CreateDivisorSession(); - try - { - ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; - byte[] hits = new byte[exponents.Length]; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; + byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData); - session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 11UL, divisorData); + session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); - } - finally - { - session.Return(); - } - } + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs index 8cb8323..da7ef8a 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs @@ -2,366 +2,440 @@ using System.Reflection; using FluentAssertions; using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; public class MersenneNumberDivisorGpuTesterTests { - [Theory] - [InlineData(33UL, 7UL, true)] - [InlineData(35UL, 31UL, true)] - [InlineData(37UL, 223UL, true)] - [InlineData(33UL, 13UL, false)] - [InlineData(35UL, 73UL, false)] - [InlineData(37UL, 227UL, false)] - [Trait("Category", "Fast")] - public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected) - { - var tester = new MersenneNumberDivisorGpuTester(); - ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor); - tester.IsDivisible(exponent, in divisorValue).Should().Be(expected); - } + [Theory] + [InlineData(33UL, 7UL, true)] + [InlineData(35UL, 31UL, true)] + [InlineData(37UL, 223UL, true)] + [InlineData(33UL, 13UL, false)] + [InlineData(35UL, 73UL, false)] + [InlineData(37UL, 227UL, false)] + [Trait("Category", "Fast")] + public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected) + { + var tester = new MersenneNumberDivisorGpuTester(); + ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor); + tester.IsDivisible(exponent, in divisorValue).Should().Be(expected); + } - [Fact] - [Trait("Category", "Fast")] - public void IsDivisible_handles_large_divisors() - { - var tester = new MersenneNumberDivisorGpuTester(); - UInt128 divisor = (UInt128.One << 65) - UInt128.One; - ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor); - tester.IsDivisible(65UL, in divisorValue).Should().BeTrue(); - divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate. - tester.IsDivisible(65UL, in divisorValue).Should().BeFalse(); - } + [Fact] + [Trait("Category", "Fast")] + public void IsDivisible_handles_large_divisors() + { + var tester = new MersenneNumberDivisorGpuTester(); + UInt128 divisor = (UInt128.One << 65) - UInt128.One; + ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor); + tester.IsDivisible(65UL, in divisorValue).Should().BeTrue(); + divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate. + tester.IsDivisible(65UL, in divisorValue).Should().BeFalse(); + } - [Fact] - [Trait("Category", "Fast")] - public void IsPrime_sets_divisorsExhausted_false_when_search_range_not_exhausted() - { - var tester = new MersenneNumberDivisorGpuTester(); - typeof(MersenneNumberDivisorGpuTester) - .GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)! - .SetValue(null, Array.Empty<(ulong, uint)>()); + [Fact] + [Trait("Category", "Fast")] + public void IsPrime_sets_divisorsExhausted_false_when_search_range_not_exhausted() + { + var tester = new MersenneNumberDivisorGpuTester(); + typeof(MersenneNumberDivisorGpuTester) + .GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)! + .SetValue(null, Array.Empty<(ulong, uint)>()); - tester.IsPrime(31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue(); - divisorsExhausted.Should().BeFalse(); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, 31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue(); + divisorsExhausted.Should().BeFalse(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor() - { - var tester = new MersenneNumberDivisorGpuTester(); - tester.IsPrime(35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse(); - divisorsExhausted.Should().BeTrue(); - } + [Fact] + [Trait("Category", "Fast")] + public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor() + { + var tester = new MersenneNumberDivisorGpuTester(); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, 35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse(); + divisorsExhausted.Should().BeTrue(); - [Fact] - [Trait("Category", "Fast")] - public void IsPrime_accepts_large_search_limits() - { - var tester = new MersenneNumberDivisorGpuTester(); - tester.IsPrime(35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse(); - exhausted.Should().BeTrue(); - } + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_tester_tracks_divisors_across_primes() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester(); - tester.ConfigureFromMaxPrime(43UL); + [Fact] + [Trait("Category", "Fast")] + public void IsPrime_accepts_large_search_limits() + { + var tester = new MersenneNumberDivisorGpuTester(); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, 35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse(); + exhausted.Should().BeTrue(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - tester.IsPrime(31UL, out bool exhausted, out ulong divisor).Should().BeTrue(); - exhausted.Should().BeTrue(); - divisor.Should().Be(0UL); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_tester_tracks_divisors_across_primes() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester(); + tester.ConfigureFromMaxPrime(43UL); - tester.IsPrime(37UL, out exhausted, out divisor).Should().BeFalse(); - exhausted.Should().BeTrue(); - divisor.Should().BeGreaterThan(0UL); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, 31UL, out bool exhausted, out ulong divisor).Should().BeTrue(); + exhausted.Should().BeTrue(); + divisor.Should().Be(0UL); - tester.IsPrime(43UL, out exhausted, out divisor).Should().BeFalse(); - exhausted.Should().BeTrue(); - divisor.Should().BeGreaterThan(0UL); - } + tester.IsPrime(gpu, 37UL, out exhausted, out divisor).Should().BeFalse(); + exhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_tester_defaults_to_zero_limit_before_configuration() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester(); + tester.IsPrime(gpu, 43UL, out exhausted, out divisor).Should().BeFalse(); + exhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - tester.DivisorLimit.Should().Be(0UL); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_tester_defaults_to_zero_limit_before_configuration() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester(); - ulong[] primes = { 31UL, 37UL }; - ulong[] allowed = new ulong[primes.Length]; + tester.DivisorLimit.Should().Be(0UL); - tester.PrepareCandidates(primes, allowed); - allowed.Should().OnlyContain(value => value == 0UL); - } + ulong[] primes = { 31UL, 37UL }; + ulong[] allowed = new ulong[primes.Length]; - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_tester_respects_filter_based_limit() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester(); - tester.ConfigureFromMaxPrime(41UL); + tester.PrepareCandidates(primes, allowed); + allowed.Should().OnlyContain(value => value == 0UL); + } - tester.IsPrime(31UL, out _, out ulong divisor).Should().BeTrue(); - divisor.Should().Be(0UL); - tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse(); - exhausted.Should().BeTrue(); - divisor.Should().BeGreaterThan(0UL); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_tester_respects_filter_based_limit() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester(); + tester.ConfigureFromMaxPrime(41UL); - tester.IsPrime(41UL, out bool divisorsExhausted, out divisor).Should().BeFalse(); - divisorsExhausted.Should().BeTrue(); - divisor.Should().BeGreaterThan(0UL); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, 31UL, out _, out ulong divisor).Should().BeTrue(); + divisor.Should().Be(0UL); + tester.IsPrime(gpu, 37UL, out bool exhausted, out divisor).Should().BeFalse(); + exhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); + + tester.IsPrime(gpu, 41UL, out bool divisorsExhausted, out divisor).Should().BeFalse(); + divisorsExhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Theory] - [InlineData(71UL, false)] - [InlineData(89UL, true)] - [InlineData(127UL, true)] - [Trait("Category", "Fast")] - public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime) - { - var tester = new MersenneNumberDivisorByDivisorGpuTester(); - tester.ConfigureFromMaxPrime(exponent); + [Theory] + [InlineData(71UL, false)] + [InlineData(89UL, true)] + [InlineData(127UL, true)] + [Trait("Category", "Fast")] + public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime) + { + var tester = new MersenneNumberDivisorByDivisorGpuTester(); + tester.ConfigureFromMaxPrime(exponent); - tester.IsPrime(exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime); - exhausted.Should().BeTrue(); - if (expectedPrime) - { - divisor.Should().Be(0UL); - } - else - { - divisor.Should().BeGreaterThan(0UL); - } - } - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester - { - GpuBatchSize = 8, - }; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime); - tester.ConfigureFromMaxPrime(43UL); + exhausted.Should().BeTrue(); + if (expectedPrime) + { + divisor.Should().Be(0UL); + } + else + { + divisor.Should().BeGreaterThan(0UL); + } + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } - tester.IsPrime(41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse(); - divisorsExhausted.Should().BeTrue(); - divisor.Should().BeGreaterThan(0UL); - } + } + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester + { + GpuBatchSize = 8, + }; - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_session_computes_cycle_when_zero_is_provided() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester(); - tester.ConfigureFromMaxPrime(43UL); + tester.ConfigureFromMaxPrime(43UL); - var session = tester.CreateDivisorSession(); - try - { - ulong[] primes = { 31UL, 37UL, 41UL }; - byte[] hits = new byte[primes.Length]; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsPrime(gpu, 41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse(); + divisorsExhausted.Should().BeTrue(); + divisor.Should().BeGreaterThan(0UL); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), 0UL, primes, hits); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_session_computes_cycle_when_zero_is_provided() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester(); + tester.ConfigureFromMaxPrime(43UL); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0 }); - } - finally - { - session.Return(); - } - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] primes = { 31UL, 37UL, 41UL }; + byte[] hits = new byte[primes.Length]; - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_session_checks_divisors_across_primes() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester(); - tester.ConfigureFromMaxPrime(43UL); + session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), 0UL, primes, hits); - var session = tester.CreateDivisorSession(); - try - { - ulong[] primes = { 31UL, 37UL, 41UL, 43UL }; - byte[] hits = new byte[primes.Length]; + hits.Should().ContainInOrder(new byte[] { 0, 1, 0 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL)); - session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 }); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_session_checks_divisors_across_primes() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester(); + tester.ConfigureFromMaxPrime(43UL); - Array.Fill(hits, (byte)0); - ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL)); - session.CheckDivisor(13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL), cycle13367, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); - } - finally - { - session.Return(); - } - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] primes = { 31UL, 37UL, 41UL, 43UL }; + byte[] hits = new byte[primes.Length]; - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_gpu_session_reuses_cycle_remainders_across_batches() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester - { - GpuBatchSize = 2, - }; + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL)); + session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits); + hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 }); - tester.ConfigureFromMaxPrime(47UL); + Array.Fill(hits, (byte)0); + ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(gpu, 13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL)); + session.CheckDivisor(13367UL, MontgomeryDivisorDataPool.Shared.FromModulus(13367UL), cycle13367, primes, hits); + hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - var session = tester.CreateDivisorSession(); - try - { - ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; - byte[] hits = new byte[primes.Length]; + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_gpu_session_reuses_cycle_remainders_across_batches() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester + { + GpuBatchSize = 2, + }; - ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL)); - session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits); + tester.ConfigureFromMaxPrime(47UL); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); - } - finally - { - session.Return(); - } - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; + byte[] hits = new byte[primes.Length]; - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester - { - GpuBatchSize = 5, - }; - tester.ConfigureFromMaxPrime(43UL); + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL)); + session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits); - var session = tester.CreateDivisorSession(); - try - { - ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; - byte[] hits = new byte[exponents.Length]; + hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester + { + GpuBatchSize = 5, + }; + tester.ConfigureFromMaxPrime(43UL); - session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; + byte[] hits = new byte[exponents.Length]; - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); - } - finally - { - session.Return(); - } - } + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(7UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 7UL, divisorData); - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite() - { - var tester = new MersenneNumberDivisorByDivisorGpuTester - { - GpuBatchSize = 5, - }; - tester.ConfigureFromMaxPrime(61UL); + session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); - var session = tester.CreateDivisorSession(); - try - { - ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; - byte[] hits = new byte[exponents.Length]; + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData); + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite() + { + var tester = new MersenneNumberDivisorByDivisorGpuTester + { + GpuBatchSize = 5, + }; + tester.ConfigureFromMaxPrime(61UL); - session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; + byte[] hits = new byte[exponents.Length]; - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); - } - finally - { - session.Return(); - } - } + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(11UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, 11UL, divisorData); - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation() - { - var cycles = MersenneDivisorCycles.Shared; - var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!; - var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!; + session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); - var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!; - var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles); + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - try - { - var patchedTable = new List<(ulong Divisor, ulong Cycle)>(); - ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1]; + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation() + { + var cycles = MersenneDivisorCycles.Shared; + var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!; + var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!; - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(191UL); - patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(191UL, divisorData); + var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!; + var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles); - tableField.SetValue(cycles, patchedTable); - smallCyclesField.SetValue(cycles, patchedSmall); - DivisorCycleCache.Shared.RefreshSnapshot(); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + var patchedTable = new List<(ulong Divisor, ulong Cycle)>(); + ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1]; - var tester = new MersenneNumberDivisorByDivisorGpuTester(); - tester.ConfigureFromMaxPrime(19UL); + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(191UL); + patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(gpu, 191UL, divisorData); - typeof(MersenneNumberDivisorByDivisorGpuTester) - .GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)! - .SetValue(tester, 200UL); + tableField.SetValue(cycles, patchedTable); + smallCyclesField.SetValue(cycles, patchedSmall); + DivisorCycleCache.Shared.RefreshSnapshot(); - tester.IsPrime(19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); - divisorsExhausted.Should().BeTrue(); - divisor.Should().Be(0UL); - } - finally - { - tableField.SetValue(cycles, originalTable); - smallCyclesField.SetValue(cycles, originalSmall); - DivisorCycleCache.Shared.RefreshSnapshot(); - } - } + var tester = new MersenneNumberDivisorByDivisorGpuTester(); + tester.ConfigureFromMaxPrime(19UL); - [Fact] - [Trait("Category", "Fast")] - public void ByDivisor_cpu_session_reuses_cycle_remainders_across_primes() - { - var tester = new MersenneNumberDivisorByDivisorCpuTester - { - BatchSize = 2, - }; + typeof(MersenneNumberDivisorByDivisorGpuTester) + .GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)! + .SetValue(tester, 200UL); - tester.ConfigureFromMaxPrime(47UL); + tester.IsPrime(gpu, 19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); + divisorsExhausted.Should().BeTrue(); + divisor.Should().Be(0UL); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + tableField.SetValue(cycles, originalTable); + smallCyclesField.SetValue(cycles, originalSmall); + DivisorCycleCache.Shared.RefreshSnapshot(); + } + } - var session = tester.CreateDivisorSession(); - try - { - ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; - byte[] hits = new byte[primes.Length]; + [Fact] + [Trait("Category", "Fast")] + public void ByDivisor_cpu_session_reuses_cycle_remainders_across_primes() + { + var tester = new MersenneNumberDivisorByDivisorCpuTester + { + BatchSize = 2, + }; - ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL)); - session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits); + tester.ConfigureFromMaxPrime(47UL); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); - } - finally - { - session.Return(); - } - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + var session = tester.CreateDivisorSession(gpu); + try + { + ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; + byte[] hits = new byte[primes.Length]; + + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL)); + session.CheckDivisor(223UL, MontgomeryDivisorDataPool.Shared.FromModulus(223UL), cycle223, primes, hits); + + hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); + } + finally + { + session.Return(); + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberIncrementalCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberIncrementalCpuTesterTests.cs index d9e9c80..d74cd06 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberIncrementalCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberIncrementalCpuTesterTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using PerfectNumbers.Core.Cpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; using UInt128 = System.UInt128; @@ -7,30 +8,38 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberIncrementalCpuTesterTests { - private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; - [Theory] - [InlineData(GpuKernelType.Incremental)] - [InlineData(GpuKernelType.Pow2Mod)] - [Trait("Category", "Fast")] - public void Scan_handles_various_prime_exponents(GpuKernelType type) - { - var tester = new MersenneNumberIncrementalCpuTester(type); + [Theory] + [InlineData(GpuKernelType.Incremental)] + [InlineData(GpuKernelType.Pow2Mod)] + [Trait("Category", "Fast")] + public void Scan_handles_various_prime_exponents(GpuKernelType type) + { + var tester = new MersenneNumberIncrementalCpuTester(type); - if (type == GpuKernelType.Pow2Mod) - { - RunCase(tester, 11UL, 1UL, expectedPrime: false); - RunCase(tester, 71UL, 341_860UL, expectedPrime: false); - } + if (type == GpuKernelType.Pow2Mod) + { + RunCase(tester, 11UL, 1UL, expectedPrime: false); + RunCase(tester, 71UL, 341_860UL, expectedPrime: false); + } - RunCase(tester, 89UL, 1_000UL, expectedPrime: true); - RunCase(tester, 127UL, 1_000UL, expectedPrime: true); - } + RunCase(tester, 89UL, 1_000UL, expectedPrime: true); + RunCase(tester, 127UL, 1_000UL, expectedPrime: true); + } - private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) - { - bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); - isPrime.Should().Be(expectedPrime); - } + private static void RunCase(MersenneNumberIncrementalCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) + { + bool isPrime = true; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); + isPrime.Should().Be(expectedPrime); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberOrderCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberOrderCpuTesterTests.cs index 8ce0e72..18c05c2 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberOrderCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberOrderCpuTesterTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using PerfectNumbers.Core.Cpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; using UInt128 = System.UInt128; @@ -7,31 +8,39 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberOrderCpuTesterTests { - private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; - [Theory] - [InlineData(GpuKernelType.Incremental)] - [InlineData(GpuKernelType.Pow2Mod)] - [Trait("Category", "Fast")] - public void Scan_handles_various_prime_exponents(GpuKernelType type) - { - var tester = new MersenneNumberOrderCpuTester(type); + [Theory] + [InlineData(GpuKernelType.Incremental)] + [InlineData(GpuKernelType.Pow2Mod)] + [Trait("Category", "Fast")] + public void Scan_handles_various_prime_exponents(GpuKernelType type) + { + var tester = new MersenneNumberOrderCpuTester(type); - if (type == GpuKernelType.Pow2Mod) - { - RunCase(tester, 23UL, 1UL, expectedPrime: false); - RunCase(tester, 29UL, 36UL, expectedPrime: false); - } + if (type == GpuKernelType.Pow2Mod) + { + RunCase(tester, 23UL, 1UL, expectedPrime: false); + RunCase(tester, 29UL, 36UL, expectedPrime: false); + } - RunCase(tester, 89UL, 1_000UL, expectedPrime: true); - RunCase(tester, 127UL, 1_000UL, expectedPrime: true); - } + RunCase(tester, 89UL, 1_000UL, expectedPrime: true); + RunCase(tester, 127UL, 1_000UL, expectedPrime: true); + } - private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) - { - bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); - isPrime.Should().Be(expectedPrime); - } + private static void RunCase(MersenneNumberOrderCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) + { + bool isPrime = true; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); + isPrime.Should().Be(expectedPrime); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs index de70c9c..a51b054 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberResidueCpuTesterTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using PerfectNumbers.Core.Cpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; using UInt128 = System.UInt128; @@ -7,24 +8,32 @@ namespace PerfectNumbers.Core.Tests; public class MersenneNumberResidueCpuTesterTests { - private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; + private static LastDigit GetLastDigit(ulong exponent) => (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; - [Fact] - [Trait("Category", "Fast")] - public void Scan_handles_various_prime_exponents() - { - var tester = new MersenneNumberResidueCpuTester(); + [Fact] + [Trait("Category", "Fast")] + public void Scan_handles_various_prime_exponents() + { + var tester = new MersenneNumberResidueCpuTester(); - RunCase(tester, 23UL, 1UL, expectedPrime: false); - RunCase(tester, 29UL, 36UL, expectedPrime: false); - RunCase(tester, 89UL, 1_000UL, expectedPrime: true); - RunCase(tester, 127UL, 1_000UL, expectedPrime: true); - } + RunCase(tester, 23UL, 1UL, expectedPrime: false); + RunCase(tester, 29UL, 36UL, expectedPrime: false); + RunCase(tester, 89UL, 1_000UL, expectedPrime: true); + RunCase(tester, 127UL, 1_000UL, expectedPrime: true); + } - private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) - { - bool isPrime = true; - tester.Scan(exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); - isPrime.Should().Be(expectedPrime); - } + private static void RunCase(MersenneNumberResidueCpuTester tester, ulong exponent, ulong maxK, bool expectedPrime) + { + bool isPrime = true; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.Scan(gpu, exponent, (UInt128)exponent << 1, GetLastDigit(exponent), (UInt128)maxK, ref isPrime); + isPrime.Should().Be(expectedPrime); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneNumberTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneNumberTesterTests.cs index 5933008..9619f78 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneNumberTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneNumberTesterTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; @@ -7,30 +8,54 @@ namespace PerfectNumbers.Core.Tests; [Trait("Category", "Fast")] public class MersenneNumberTesterTests { - [Theory] - [InlineData(136_000_002UL)] - [InlineData(136_000_005UL)] - public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p) - { - var tester = new MersenneNumberTester(); - tester.IsMersennePrime(p).Should().BeFalse(); - } + [Theory] + [InlineData(136_000_002UL)] + [InlineData(136_000_005UL)] + public void IsMersennePrime_detects_small_prime_divisors_for_large_exponents(ulong p) + { + var tester = new MersenneNumberTester(); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsMersennePrime(gpu, p).Should().BeFalse(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Theory] - [InlineData(125UL)] - public void WarmUpOrders_populates_cache_without_affecting_results(ulong p) - { - var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true); - tester.WarmUpOrders(p, 1_000UL); - tester.IsMersennePrime(p).Should().BeFalse(); - } + [Theory] + [InlineData(125UL)] + public void WarmUpOrders_populates_cache_without_affecting_results(ulong p) + { + var tester = new MersenneNumberTester(useOrderCache: true, useGpuOrder: true); + tester.WarmUpOrders(p, 1_000UL); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsMersennePrime(gpu, p).Should().BeFalse(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Theory] - [InlineData(125UL, true)] - [InlineData(127UL, false)] - public void SharesFactorWithExponentMinusOne_detects_common_factors(ulong p, bool expected) - { - p.SharesFactorWithExponentMinusOne().Should().Be(expected); - } + [Theory] + [InlineData(125UL, true)] + [InlineData(127UL, false)] + public void SharesFactorWithExponentMinusOne_detects_common_factors(ulong p, bool expected) + { + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + p.SharesFactorWithExponentMinusOne(gpu).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs index 7f578a9..e428761 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberTester/MersenneResidueModeTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; @@ -6,27 +7,43 @@ namespace PerfectNumbers.Core.Tests; [Trait("Category", "Fast")] public class MersenneResidueModeTests { - [Fact] - public void IsMersennePrime_residue_mode_rejects_composite_exponent() - { - var tester = new MersenneNumberTester( - useResidue: true, - useIncremental: false, - useGpuScan: false, - useGpuOrder: false); - tester.IsMersennePrime(136_000_002UL).Should().BeFalse(); - } + [Fact] + public void IsMersennePrime_residue_mode_rejects_composite_exponent() + { + var tester = new MersenneNumberTester( + useResidue: true, + useIncremental: false, + useGpuScan: false, + useGpuOrder: false); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsMersennePrime(gpu, 136_000_002UL).Should().BeFalse(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - public void IsMersennePrime_residue_mode_accepts_prime_exponent() - { - var tester = new MersenneNumberTester( - useResidue: true, - useIncremental: true, - useGpuScan: false, - useGpuOrder: false, - maxK: 1_000UL); - tester.IsMersennePrime(31UL).Should().BeTrue(); - } + [Fact] + public void IsMersennePrime_residue_mode_accepts_prime_exponent() + { + var tester = new MersenneNumberTester( + useResidue: true, + useIncremental: true, + useGpuScan: false, + useGpuOrder: false, + maxK: 1_000UL); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tester.IsMersennePrime(gpu, 31UL).Should().BeTrue(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/ModResidueTrackerTests.cs b/PerfectNumbers.Core.Tests/ModResidueTrackerTests.cs index 34eef95..230fb8e 100644 --- a/PerfectNumbers.Core.Tests/ModResidueTrackerTests.cs +++ b/PerfectNumbers.Core.Tests/ModResidueTrackerTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; @@ -6,61 +7,93 @@ namespace PerfectNumbers.Core.Tests; [Trait("Category", "Fast")] public class ModResidueTrackerTests { - [Fact] - public void IsDivisible_advances_residue_for_identity() - { - var tracker = new ModResidueTracker(ResidueModel.Identity); + [Fact] + public void IsDivisible_advances_residue_for_identity() + { + var tracker = new ModResidueTracker(ResidueModel.Identity); - tracker.IsDivisible(5, 2).Should().BeFalse(); - tracker.IsDivisible(6, 2).Should().BeTrue(); - tracker.IsDivisible(7, 2).Should().BeFalse(); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tracker.IsDivisible(gpu, 5, 2).Should().BeFalse(); + tracker.IsDivisible(gpu, 6, 2).Should().BeTrue(); + tracker.IsDivisible(gpu, 7, 2).Should().BeFalse(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Theory] - [InlineData(3UL, 7UL, true)] - [InlineData(5UL, 31UL, true)] - [InlineData(5UL, 7UL, false)] - [InlineData(11UL, 23UL, true)] - [InlineData(11UL, 31UL, false)] - public void IsDivisible_handles_mersenne_model(ulong p, ulong divisor, bool expected) - { - var tracker = new ModResidueTracker(ResidueModel.Mersenne); + [Theory] + [InlineData(3UL, 7UL, true)] + [InlineData(5UL, 31UL, true)] + [InlineData(5UL, 7UL, false)] + [InlineData(11UL, 23UL, true)] + [InlineData(11UL, 31UL, false)] + public void IsDivisible_handles_mersenne_model(ulong p, ulong divisor, bool expected) + { + var tracker = new ModResidueTracker(ResidueModel.Mersenne); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); - tracker.IsDivisible(p, divisor).Should().Be(expected); - } + try + { + tracker.IsDivisible(gpu, p, divisor).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - public void MergeOrAppend_tracks_existing_and_new_divisors() - { - var tracker = new ModResidueTracker(ResidueModel.Identity); + [Fact] + public void MergeOrAppend_tracks_existing_and_new_divisors() + { + var tracker = new ModResidueTracker(ResidueModel.Identity); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); - tracker.BeginMerge(10); - tracker.MergeOrAppend(10, 3, out bool d3).Should().BeTrue(); - d3.Should().BeFalse(); - tracker.MergeOrAppend(10, 5, out bool d5).Should().BeTrue(); - d5.Should().BeTrue(); + try + { + tracker.BeginMerge(10); + tracker.MergeOrAppend(gpu, 10, 3, out bool d3).Should().BeTrue(); + d3.Should().BeFalse(); + tracker.MergeOrAppend(gpu, 10, 5, out bool d5).Should().BeTrue(); + d5.Should().BeTrue(); - tracker.BeginMerge(10); - tracker.MergeOrAppend(10, 4, out bool d4).Should().BeTrue(); - d4.Should().BeFalse(); + tracker.BeginMerge(10); + tracker.MergeOrAppend(gpu, 10, 4, out bool d4).Should().BeTrue(); + d4.Should().BeFalse(); - tracker.BeginMerge(11); - tracker.MergeOrAppend(11, 3, out d3).Should().BeTrue(); - d3.Should().BeFalse(); - tracker.MergeOrAppend(11, 4, out d4).Should().BeTrue(); - d4.Should().BeFalse(); - tracker.MergeOrAppend(11, 5, out d5).Should().BeTrue(); - d5.Should().BeFalse(); - } + tracker.BeginMerge(11); + tracker.MergeOrAppend(gpu, 11, 3, out d3).Should().BeTrue(); + d3.Should().BeFalse(); + tracker.MergeOrAppend(gpu, 11, 4, out d4).Should().BeTrue(); + d4.Should().BeFalse(); + tracker.MergeOrAppend(gpu, 11, 5, out d5).Should().BeTrue(); + d5.Should().BeFalse(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - public void IsDivisible_requires_non_decreasing_numbers() - { - var tracker = new ModResidueTracker(ResidueModel.Identity); - tracker.IsDivisible(5, 2); + [Fact] + public void IsDivisible_requires_non_decreasing_numbers() + { + var tracker = new ModResidueTracker(ResidueModel.Identity); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + tracker.IsDivisible(gpu, 5, 2); - Action act = () => tracker.IsDivisible(4, 2); - act.Should().Throw(); - } + Action act = () => tracker.IsDivisible(gpu, 4, 2); + act.Should().Throw(); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs b/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs index 19eaaa8..304248c 100644 --- a/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs +++ b/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs @@ -1,198 +1,236 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; public class PrimeOrderCalculatorTests { - public static IEnumerable SmallPrimes() - { - yield return new object[] { 3UL }; - yield return new object[] { 5UL }; - yield return new object[] { 7UL }; - yield return new object[] { 11UL }; - yield return new object[] { 13UL }; - yield return new object[] { 17UL }; - yield return new object[] { 19UL }; - yield return new object[] { 31UL }; - yield return new object[] { 61UL }; - yield return new object[] { 89UL }; - yield return new object[] { 127UL }; - yield return new object[] { 521UL }; - } + public static IEnumerable SmallPrimes() + { + yield return new object[] { 3UL }; + yield return new object[] { 5UL }; + yield return new object[] { 7UL }; + yield return new object[] { 11UL }; + yield return new object[] { 13UL }; + yield return new object[] { 17UL }; + yield return new object[] { 19UL }; + yield return new object[] { 31UL }; + yield return new object[] { 61UL }; + yield return new object[] { 89UL }; + yield return new object[] { 127UL }; + yield return new object[] { 521UL }; + } - public static IEnumerable PrimesWithPrevious() - { - yield return new object[] { 5UL, 3UL }; - yield return new object[] { 7UL, 5UL }; - yield return new object[] { 11UL, 7UL }; - yield return new object[] { 13UL, 11UL }; - yield return new object[] { 17UL, 13UL }; - yield return new object[] { 19UL, 17UL }; - yield return new object[] { 31UL, 29UL }; - yield return new object[] { 61UL, 59UL }; - yield return new object[] { 89UL, 83UL }; - yield return new object[] { 127UL, 113UL }; - yield return new object[] { 521UL, 509UL }; - } + public static IEnumerable PrimesWithPrevious() + { + yield return new object[] { 5UL, 3UL }; + yield return new object[] { 7UL, 5UL }; + yield return new object[] { 11UL, 7UL }; + yield return new object[] { 13UL, 11UL }; + yield return new object[] { 17UL, 13UL }; + yield return new object[] { 19UL, 17UL }; + yield return new object[] { 31UL, 29UL }; + yield return new object[] { 61UL, 59UL }; + yield return new object[] { 89UL, 83UL }; + yield return new object[] { 127UL, 113UL }; + yield return new object[] { 521UL, 509UL }; + } - [Theory] - [Trait("Category", "Fast")] - [MemberData(nameof(SmallPrimes))] - public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime) - { - ulong expected = ComputeOrderByDoubling(prime); + [Theory] + [Trait("Category", "Fast")] + [MemberData(nameof(SmallPrimes))] + public void Calculate_heuristic_mode_matches_naive_cycle_length(ulong prime) + { + ulong expected = ComputeOrderByDoubling(prime); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); - ulong result = PrimeOrderCalculator.Calculate( - prime, - previousOrder: null, - MontgomeryDivisorDataPool.Shared.FromModulus(prime), - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + ulong result = PrimeOrderCalculator.Calculate( + gpu, + prime, + previousOrder: null, + MontgomeryDivisorDataPool.Shared.FromModulus(prime), + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - result.Should().Be(expected); - } + PrimeOrderCalculatorAccelerator.Return(gpu); - [Theory] - [Trait("Category", "Fast")] - [MemberData(nameof(PrimesWithPrevious))] - public void Calculate_heuristic_mode_matches_strict_mode_even_with_previous_order(ulong prime, ulong previousPrime) - { - ulong previousOrder = ComputeOrderByDoubling(previousPrime); + result.Should().Be(expected); + } - ulong heuristic = PrimeOrderCalculator.Calculate( - prime, - previousOrder, - MontgomeryDivisorDataPool.Shared.FromModulus(prime), - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + [Theory] + [Trait("Category", "Fast")] + [MemberData(nameof(PrimesWithPrevious))] + public void Calculate_heuristic_mode_matches_strict_mode_even_with_previous_order(ulong prime, ulong previousPrime) + { + ulong previousOrder = ComputeOrderByDoubling(previousPrime); - ulong strict = PrimeOrderCalculator.Calculate( - prime, - previousOrder: null, - MontgomeryDivisorDataPool.Shared.FromModulus(prime), - PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + ulong heuristic = PrimeOrderCalculator.Calculate( + gpu, + prime, + previousOrder, + MontgomeryDivisorDataPool.Shared.FromModulus(prime), + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - heuristic.Should().Be(strict); - } + ulong strict = PrimeOrderCalculator.Calculate( + gpu, + prime, + previousOrder: null, + MontgomeryDivisorDataPool.Shared.FromModulus(prime), + PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - [Theory] - [Trait("Category", "Fast")] - [InlineData(2UL, 1UL)] - [InlineData(3UL, 2UL)] - public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder) - { - ulong result = PrimeOrderCalculator.Calculate( - prime, - previousOrder: null, - MontgomeryDivisorDataPool.Shared.FromModulus(prime), - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + PrimeOrderCalculatorAccelerator.Return(gpu); - result.Should().Be(expectedOrder); - } + heuristic.Should().Be(strict); + } - [Fact] - [Trait("Category", "Fast")] - public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored() - { - var config = new PrimeOrderCalculator.PrimeOrderSearchConfig( - smallFactorLimit: 1, - pollardRhoMilliseconds: 0, - maxPowChecks: 8, - mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic); + [Theory] + [Trait("Category", "Fast")] + [InlineData(2UL, 1UL)] + [InlineData(3UL, 2UL)] + public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder) + { + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + ulong result = PrimeOrderCalculator.Calculate( + gpu, + prime, + previousOrder: null, + MontgomeryDivisorDataPool.Shared.FromModulus(prime), + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - ulong result = PrimeOrderCalculator.Calculate( - prime: 13UL, - previousOrder: null, - MontgomeryDivisorDataPool.Shared.FromModulus(13UL), - config, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + PrimeOrderCalculatorAccelerator.Return(gpu); + result.Should().Be(expectedOrder); + } - result.Should().Be(ComputeOrderByDoubling(13UL)); - } + [Fact] + [Trait("Category", "Fast")] + public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored() + { + var config = new PrimeOrderCalculator.PrimeOrderSearchConfig( + smallFactorLimit: 1, + pollardRhoMilliseconds: 0, + maxPowChecks: 8, + mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic); - [Fact] - [Trait("Category", "Fast")] - public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode() - { - var heuristicConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig( - smallFactorLimit: 2, - pollardRhoMilliseconds: 0, - maxPowChecks: 1, - mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + ulong result = PrimeOrderCalculator.Calculate( + gpu, + prime: 13UL, + previousOrder: null, + MontgomeryDivisorDataPool.Shared.FromModulus(13UL), + config, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(239UL); - ulong heuristic = PrimeOrderCalculator.Calculate( - prime: 239UL, - previousOrder: null, - divisorData, - heuristicConfig, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + PrimeOrderCalculatorAccelerator.Return(gpu); + result.Should().Be(ComputeOrderByDoubling(13UL)); + } - heuristic.Should().Be(ComputeOrderByDoubling(239UL)); + [Fact] + [Trait("Category", "Fast")] + public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode() + { + var heuristicConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig( + smallFactorLimit: 2, + pollardRhoMilliseconds: 0, + maxPowChecks: 1, + mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic); - var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig( - smallFactorLimit: 2, - pollardRhoMilliseconds: 0, - maxPowChecks: 1, - mode: PrimeOrderCalculator.PrimeOrderMode.Strict); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(239UL); + ulong heuristic = PrimeOrderCalculator.Calculate( + gpu, + prime: 239UL, + previousOrder: null, + divisorData, + heuristicConfig, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - ulong strict = PrimeOrderCalculator.Calculate( - prime: 239UL, - previousOrder: null, - divisorData, - strictConfig, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + heuristic.Should().Be(ComputeOrderByDoubling(239UL)); - strict.Should().Be(heuristic); - } + var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig( + smallFactorLimit: 2, + pollardRhoMilliseconds: 0, + maxPowChecks: 1, + mode: PrimeOrderCalculator.PrimeOrderMode.Strict); - [Fact] - [Trait("Category", "Fast")] - public void Calculate_handles_128_bit_prime() - { - UInt128 prime = UInt128.Parse("18446744073709641691"); + ulong strict = PrimeOrderCalculator.Calculate( + gpu, + prime: 239UL, + previousOrder: null, + divisorData, + strictConfig, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - UInt128 result = PrimeOrderCalculator.Calculate( - prime, - previousOrder: null, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, - PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); + strict.Should().Be(heuristic); - result.Should().Be((UInt128)1229782938247309446UL); - } + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void GetCycle_uses_wide_prime_heuristic() - { - UInt128 prime = UInt128.Parse("18446744073709641691"); - MersenneDivisorCycles.GetCycle(prime).Should().Be((UInt128)1229782938247309446UL); - } + [Fact] + [Trait("Category", "Fast")] + public void Calculate_handles_128_bit_prime() + { + UInt128 prime = UInt128.Parse("18446744073709641691"); - private static ulong ComputeOrderByDoubling(ulong prime) - { - if (prime == 2UL) - { - return 1UL; - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + UInt128 result = PrimeOrderCalculator.Calculate( + gpu, + prime, + previousOrder: null, + PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, + PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu); - if (prime == 3UL) - { - return 2UL; - } + PrimeOrderCalculatorAccelerator.Return(gpu); + result.Should().Be((UInt128)1229782938247309446UL); + } - ulong order = 1UL; - ulong value = 2UL % prime; + [Fact] + [Trait("Category", "Fast")] + public void GetCycle_uses_wide_prime_heuristic() + { + UInt128 prime = UInt128.Parse("18446744073709641691"); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + var mersenneDivisorCycles = MersenneDivisorCycles.GetCycle(gpu, prime).Should().Be((UInt128)1229782938247309446UL); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - while (value != 1UL) - { - value = (value * 2UL) % prime; - order++; - } + private static ulong ComputeOrderByDoubling(ulong prime) + { + if (prime == 2UL) + { + return 1UL; + } - return order; - } + if (prime == 3UL) + { + return 2UL; + } + + ulong order = 1UL; + ulong value = 2UL % prime; + + while (value != 1UL) + { + value = (value * 2UL) % prime; + order++; + } + + return order; + } } diff --git a/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterGcdTests.cs b/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterGcdTests.cs index 3b36034..b2ec849 100644 --- a/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterGcdTests.cs +++ b/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterGcdTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; @@ -6,20 +7,28 @@ namespace PerfectNumbers.Core.Tests; [Trait("Category", "Fast")] public class PrimeTesterGcdTests { - [Theory] - [InlineData(81UL, true)] - [InlineData(101UL, false)] - public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected) - { - PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected); - } + [Theory] + [InlineData(81UL, true)] + [InlineData(101UL, false)] + public void SharesFactorWithMaxExponent_detects_gcd(ulong n, bool expected) + { + PrimeTester.SharesFactorWithMaxExponent(n).Should().Be(expected); + } - [Fact] - public void SharesFactorWithMaxExponentBatch_filters_values() - { - ulong[] values = [81UL, 101UL]; - Span results = stackalloc byte[values.Length]; - PrimeTester.SharesFactorWithMaxExponentBatch(values, results); - results.ToArray().Should().Equal(new byte[] { 1, 0 }); - } + [Fact] + public void SharesFactorWithMaxExponentBatch_filters_values() + { + ulong[] values = [81UL, 101UL]; + Span results = stackalloc byte[values.Length]; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + PrimeTester.SharesFactorWithMaxExponentBatch(gpu, values, results); + results.ToArray().Should().Equal(new byte[] { 1, 0 }); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs b/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs index 72dbcfb..383fc28 100644 --- a/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs +++ b/PerfectNumbers.Core.Tests/PrimeTester/PrimeTesterIsPrimeTests.cs @@ -1,81 +1,98 @@ using FluentAssertions; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; public class PrimeTesterIsPrimeTests { - [Theory] - [Trait("Category", "Fast")] - // EvenPerfectBitScanner never routes the tiny values anymore; keep coverage focused on the admissible range handled by production scans so the guards in PrimeTester stay commented out without breaking validation. - [InlineData(7UL, true)] - [InlineData(9UL, false)] - [InlineData(11UL, true)] - [InlineData(13UL, true)] - [InlineData(21UL, false)] - [InlineData(97UL, true)] - [InlineData(133UL, false)] - [InlineData(137UL, true)] - [InlineData(143UL, false)] - [InlineData(199UL, true)] - [InlineData(209UL, false)] - [InlineData(403UL, false)] - [InlineData(341UL, false)] - public void IsPrime_returns_expected_results(ulong n, bool expected) - { - var tester = new PrimeTester(); + [Theory] + [Trait("Category", "Fast")] + // EvenPerfectBitScanner never routes the tiny values anymore; keep coverage focused on the admissible range handled by production scans so the guards in PrimeTester stay commented out without breaking validation. + [InlineData(7UL, true)] + [InlineData(9UL, false)] + [InlineData(11UL, true)] + [InlineData(13UL, true)] + [InlineData(21UL, false)] + [InlineData(97UL, true)] + [InlineData(133UL, false)] + [InlineData(137UL, true)] + [InlineData(143UL, false)] + [InlineData(199UL, true)] + [InlineData(209UL, false)] + [InlineData(403UL, false)] + [InlineData(341UL, false)] + public void IsPrime_returns_expected_results(ulong n, bool expected) + { + var tester = new PrimeTester(); PrimeTester.IsPrime(n).Should().Be(expected); - } + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(139UL, true)] - [InlineData(189UL, false)] - [InlineData(193UL, true)] - [InlineData(279UL, false)] - public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected) - { - var tester = new PrimeTester(); + [Theory] + [Trait("Category", "Fast")] + [InlineData(139UL, true)] + [InlineData(189UL, false)] + [InlineData(193UL, true)] + [InlineData(279UL, false)] + public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected) + { + var tester = new PrimeTester(); - PrimeTester.IsPrimeGpu(n).Should().Be(expected); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + PrimeTester.IsPrimeGpu(gpu, n).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Fact] - [Trait("Category", "Fast")] - public void IsPrimeGpu_falls_back_to_cpu_when_forced() - { - var tester = new PrimeTester(); - PrimeTester.IsPrime(137UL).Should().BeTrue(); - PrimeTester.IsPrime(341UL).Should().BeFalse(); - } + [Fact] + [Trait("Category", "Fast")] + public void IsPrimeGpu_falls_back_to_cpu_when_forced() + { + var tester = new PrimeTester(); + PrimeTester.IsPrime(137UL).Should().BeTrue(); + PrimeTester.IsPrime(341UL).Should().BeFalse(); + } - [Fact] - [Trait("Category", "Fast")] - public void IsPrimeGpu_handles_parallel_calls_for_known_primes() - { - GpuPrimeWorkLimiter.SetLimit(64); - PrimeTester.GpuBatchSize = 256_000; + [Fact] + [Trait("Category", "Fast")] + public void IsPrimeGpu_handles_parallel_calls_for_known_primes() + { + GpuPrimeWorkLimiter.SetLimit(64); + PrimeTester.GpuBatchSize = 256_000; - ulong[] primes = [107UL, 127UL, 521UL, 607UL]; - var results = new bool[primes.Length]; + ulong[] primes = [107UL, 127UL, 521UL, 607UL]; + var results = new bool[primes.Length]; - var testers = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); - Parallel.For(0, primes.Length, i => - { - results[i] = PrimeTester.IsPrimeGpu(primes[i]); - }); + var testers = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + Parallel.For(0, primes.Length, i => + { + results[i] = PrimeTester.IsPrimeGpu(gpu, primes[i]); + }); - results.Should().AllBeEquivalentTo(true); + results.Should().AllBeEquivalentTo(true); - Parallel.For(0, Environment.ProcessorCount * 16, _ => - { - foreach (ulong prime in primes) - { - PrimeTester.IsPrimeGpu(prime).Should().BeTrue(); - } - }); - } + Parallel.For(0, Environment.ProcessorCount * 16, _ => + { + foreach (ulong prime in primes) + { + PrimeTester.IsPrimeGpu(gpu, prime).Should().BeTrue(); + } + }); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } } diff --git a/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs b/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs index 80916da..6194fe9 100644 --- a/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs +++ b/PerfectNumbers.Core.Tests/UInt128Extensions/UInt128ExtensionsTests.cs @@ -1,284 +1,293 @@ using System.Numerics; using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; public class UInt128ExtensionsTests { - public static IEnumerable BinaryGcdCases() - { - yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 }; - yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 }; + public static IEnumerable BinaryGcdCases() + { + yield return new object[] { UInt128.Zero, (UInt128)5, (UInt128)5 }; + yield return new object[] { (UInt128)18, (UInt128)24, (UInt128)6 }; - UInt128 big1 = (UInt128.One << 96) + 12345; - UInt128 big2 = (UInt128.One << 80) + 67890; - yield return new object[] - { - big1, - big2, - (UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2) - }; - } + UInt128 big1 = (UInt128.One << 96) + 12345; + UInt128 big2 = (UInt128.One << 80) + 67890; + yield return new object[] + { + big1, + big2, + (UInt128)BigInteger.GreatestCommonDivisor((BigInteger)big1, (BigInteger)big2) + }; + } - [Theory] - [Trait("Category", "Fast")] - [MemberData(nameof(BinaryGcdCases))] - public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected) - { - a.BinaryGcd(b).Should().Be(expected); - } + [Theory] + [Trait("Category", "Fast")] + [MemberData(nameof(BinaryGcdCases))] + public void BinaryGcd_matches_BigInteger(UInt128 a, UInt128 b, UInt128 expected) + { + a.BinaryGcd(b).Should().Be(expected); + } - public static IEnumerable TrailingZeroCases() - { - yield return new object[] { UInt128.Zero, 128 }; - yield return new object[] { (UInt128)1, 0 }; - yield return new object[] { (UInt128)8, 3 }; - yield return new object[] { UInt128.One << 80, 80 }; - } + public static IEnumerable TrailingZeroCases() + { + yield return new object[] { UInt128.Zero, 128 }; + yield return new object[] { (UInt128)1, 0 }; + yield return new object[] { (UInt128)8, 3 }; + yield return new object[] { UInt128.One << 80, 80 }; + } - [Theory] - [Trait("Category", "Fast")] - [MemberData(nameof(TrailingZeroCases))] - public void CountTrailingZeros_returns_expected(UInt128 value, int expected) - { - value.CountTrailingZeros().Should().Be(expected); - } + [Theory] + [Trait("Category", "Fast")] + [MemberData(nameof(TrailingZeroCases))] + public void CountTrailingZeros_returns_expected(UInt128 value, int expected) + { + value.CountTrailingZeros().Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(3UL)] - [InlineData(5UL)] - [InlineData(7UL)] - public void CalculateOrder_matches_naive(ulong q) - { - UInt128 q128 = q; - ulong expected = NaiveOrder(q); - q128.CalculateOrder().Should().Be(expected); - } + [Theory] + [Trait("Category", "Fast")] + [InlineData(3UL)] + [InlineData(5UL)] + [InlineData(7UL)] + public void CalculateOrder_matches_naive(ulong q) + { + UInt128 q128 = q; + ulong expected = NaiveOrder(q); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + q128.CalculateOrder(gpu).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - private static ulong NaiveOrder(ulong q) - { - if (q <= 2UL) - { - return 0UL; - } + private static ulong NaiveOrder(ulong q) + { + if (q <= 2UL) + { + return 0UL; + } - ulong order = 1UL; - ulong value = 2UL % q; - while (value != 1UL) - { - value = (value << 1) % q; - order++; - } + ulong order = 1UL; + ulong value = 2UL % q; + while (value != 1UL) + { + value = (value << 1) % q; + order++; + } - return order; - } + return order; + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(2UL, true)] - [InlineData(9UL, false)] - [InlineData(97UL, true)] - public void IsPrimeCandidate_checks_small(ulong n, bool expected) - { - ((UInt128)n).IsPrimeCandidate().Should().Be(expected); - } + [Theory] + [Trait("Category", "Fast")] + [InlineData(2UL, true)] + [InlineData(9UL, false)] + [InlineData(97UL, true)] + public void IsPrimeCandidate_checks_small(ulong n, bool expected) + { + ((UInt128)n).IsPrimeCandidate().Should().Be(expected); + } - [Fact] - [Trait("Category", "Fast")] - public void IsPrimeCandidate_detects_large_composite() - { - UInt128 n = (UInt128.One << 64) + 1; - n.IsPrimeCandidate().Should().BeFalse(); - } + [Fact] + [Trait("Category", "Fast")] + public void IsPrimeCandidate_detects_large_composite() + { + UInt128 n = (UInt128.One << 64) + 1; + n.IsPrimeCandidate().Should().BeFalse(); + } - [Fact] - [Trait("Category", "Fast")] - public void Mod10_8_5_3_matches_operator() - { - UInt128 value = (UInt128.One << 100) + 12345678901234567890UL; + [Fact] + [Trait("Category", "Fast")] + public void Mod10_8_5_3_matches_operator() + { + UInt128 value = (UInt128.One << 100) + 12345678901234567890UL; - value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3); + value.Mod10_8_5_3(out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3); - ulong expected10 = (ulong)(value % 10); - ulong expected8 = (ulong)(value % 8); - ulong expected5 = (ulong)(value % 5); - ulong expected3 = (ulong)(value % 3); + ulong expected10 = (ulong)(value % 10); + ulong expected8 = (ulong)(value % 8); + ulong expected5 = (ulong)(value % 5); + ulong expected3 = (ulong)(value % 3); - mod10.Should().Be(expected10); - mod8.Should().Be(expected8); - mod5.Should().Be(expected5); - mod3.Should().Be(expected3); - } + mod10.Should().Be(expected10); + mod8.Should().Be(expected8); + mod5.Should().Be(expected5); + mod3.Should().Be(expected3); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(2UL, 0UL)] - [InlineData(2UL, 1UL)] - [InlineData(2UL, 63UL)] - [InlineData(2UL, 127UL)] - [InlineData(3UL, 5UL)] - public void Pow_matches_BigInteger(ulong value, ulong exponent) - { - UInt128 v = value; - UInt128 expected = (UInt128)BigPow(value, exponent); + [Theory] + [Trait("Category", "Fast")] + [InlineData(2UL, 0UL)] + [InlineData(2UL, 1UL)] + [InlineData(2UL, 63UL)] + [InlineData(2UL, 127UL)] + [InlineData(3UL, 5UL)] + public void Pow_matches_BigInteger(ulong value, ulong exponent) + { + UInt128 v = value; + UInt128 expected = (UInt128)BigPow(value, exponent); - v.Pow(exponent).Should().Be(expected); - } + v.Pow(exponent).Should().Be(expected); + } - private static BigInteger BigPow(ulong value, ulong exponent) - { - BigInteger result = BigInteger.One; - BigInteger baseValue = value; + private static BigInteger BigPow(ulong value, ulong exponent) + { + BigInteger result = BigInteger.One; + BigInteger baseValue = value; - while (exponent != 0UL) - { - if ((exponent & 1UL) != 0UL) - { - result *= baseValue; - } + while (exponent != 0UL) + { + if ((exponent & 1UL) != 0UL) + { + result *= baseValue; + } - baseValue *= baseValue; - exponent >>= 1; - } + baseValue *= baseValue; + exponent >>= 1; + } - return result; - } + return result; + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(2UL, 10UL, 17UL)] - [InlineData(123456789UL, 3UL, 97UL)] - public void ModPow_matches_BigInteger(ulong value, ulong exponent, ulong modulus) - { - UInt128 v = value; - UInt128 e = exponent; - UInt128 m = modulus; + [Theory] + [Trait("Category", "Fast")] + [InlineData(2UL, 10UL, 17UL)] + [InlineData(123456789UL, 3UL, 97UL)] + public void ModPow_matches_BigInteger(ulong value, ulong exponent, ulong modulus) + { + UInt128 v = value; + UInt128 e = exponent; + UInt128 m = modulus; - UInt128 expected = (UInt128)BigInteger.ModPow(value, exponent, modulus); + UInt128 expected = (UInt128)BigInteger.ModPow(value, exponent, modulus); - v.ModPow(e, m).Should().Be(expected); - } + v.ModPow(e, m).Should().Be(expected); + } - [Fact] - [Trait("Category", "Fast")] - public void ModPow_handles_large_values() - { - UInt128 v = (UInt128.One << 96) + 12345; - UInt128 e = 17; - UInt128 m = 97; + [Fact] + [Trait("Category", "Fast")] + public void ModPow_handles_large_values() + { + UInt128 v = (UInt128.One << 96) + 12345; + UInt128 e = 17; + UInt128 m = 97; - UInt128 expected = (UInt128)BigInteger.ModPow((BigInteger)v, (BigInteger)e, (BigInteger)m); + UInt128 expected = (UInt128)BigInteger.ModPow((BigInteger)v, (BigInteger)e, (BigInteger)m); - v.ModPow(e, m).Should().Be(expected); - } + v.ModPow(e, m).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(0UL)] - [InlineData(1UL)] - [InlineData(123456789UL)] - public void Mod10_matches_operator(ulong low) - { - UInt128 value = (UInt128.One << 100) + low; - value.Mod10().Should().Be((ulong)(value % 10)); - } + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(1UL)] + [InlineData(123456789UL)] + public void Mod10_matches_operator(ulong low) + { + UInt128 value = (UInt128.One << 100) + low; + value.Mod10().Should().Be((ulong)(value % 10)); + } - [Fact] - [Trait("Category", "Fast")] - public void Mod8_matches_operator() - { - UInt128 value = (UInt128.One << 100) + 12345; - value.Mod8().Should().Be((ulong)(value % 8)); - } + [Fact] + [Trait("Category", "Fast")] + public void Mod8_matches_operator() + { + UInt128 value = (UInt128.One << 100) + 12345; + value.Mod8().Should().Be((ulong)(value % 8)); + } - [Fact] - [Trait("Category", "Fast")] - public void Mod3_matches_operator() - { - UInt128 value = (UInt128.One << 100) + 12345; - value.Mod3().Should().Be((ulong)(value % 3)); - } + [Fact] + [Trait("Category", "Fast")] + public void Mod3_matches_operator() + { + UInt128 value = (UInt128.One << 100) + 12345; + value.Mod3().Should().Be((ulong)(value % 3)); + } - [Fact] - [Trait("Category", "Fast")] - public void Mod6_matches_operator() - { - UInt128 value = (UInt128.One << 100) + 9876543210UL; - value.Mod6().Should().Be((ulong)(value % 6)); - } + [Fact] + [Trait("Category", "Fast")] + public void Mod6_matches_operator() + { + UInt128 value = (UInt128.One << 100) + 9876543210UL; + value.Mod6().Should().Be((ulong)(value % 6)); + } - [Fact] - [Trait("Category", "Fast")] - public void Mod5_matches_operator() - { - UInt128 value = (UInt128.One << 100) + 12345; - value.Mod5().Should().Be((ulong)(value % 5)); - } + [Fact] + [Trait("Category", "Fast")] + public void Mod5_matches_operator() + { + UInt128 value = (UInt128.One << 100) + 12345; + value.Mod5().Should().Be((ulong)(value % 5)); + } - [Fact] - [Trait("Category", "Fast")] - public void Mod7_matches_operator() - { - UInt128 value = (UInt128.One << 100) + 9876543210987654321UL; - value.Mod7().Should().Be((ulong)(value % 7)); - } + [Fact] + [Trait("Category", "Fast")] + public void Mod7_matches_operator() + { + UInt128 value = (UInt128.One << 100) + 9876543210987654321UL; + value.Mod7().Should().Be((ulong)(value % 7)); + } - [Fact] - [Trait("Category", "Fast")] - public void Mod11_matches_operator() - { - UInt128 value = (UInt128.One << 120) + 9876543210987654321UL; - value.Mod11().Should().Be((ulong)(value % 11)); - } + [Fact] + [Trait("Category", "Fast")] + public void Mod11_matches_operator() + { + UInt128 value = (UInt128.One << 120) + 9876543210987654321UL; + value.Mod11().Should().Be((ulong)(value % 11)); + } - [Fact] - [Trait("Category", "Fast")] - public void Mod128_matches_operator() - { - UInt128 value = (UInt128.One << 120) + 9876543210987654321UL; - value.Mod128().Should().Be((ulong)(value % 128)); - } + [Fact] + [Trait("Category", "Fast")] + public void Mod128_matches_operator() + { + UInt128 value = (UInt128.One << 120) + 9876543210987654321UL; + value.Mod128().Should().Be((ulong)(value % 128)); + } - [Fact] - [Trait("Category", "Fast")] - public void Mul64_matches_expected_product() - { - UInt128 a = (UInt128.One << 80) + 123456789; - UInt128 b = (UInt128.One << 64) + 987654321; + [Fact] + [Trait("Category", "Fast")] + public void Mul64_matches_expected_product() + { + UInt128 a = (UInt128.One << 80) + 123456789; + UInt128 b = (UInt128.One << 64) + 987654321; - UInt128 expected = (UInt128)((UInt128)(ulong)a * b); + UInt128 expected = (UInt128)((UInt128)(ulong)a * b); - a.Mul64(b).Should().Be(expected); - } + a.Mul64(b).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(123UL, 456UL, 789UL)] - [InlineData(123456789UL, 987654321UL, 1009UL)] - public void MulMod_matches_BigInteger(ulong a, ulong b, ulong modulus) - { - UInt128 ua = a % modulus; - UInt128 ub = b % modulus; - UInt128 mod = modulus; + [Theory] + [Trait("Category", "Fast")] + [InlineData(123UL, 456UL, 789UL)] + [InlineData(123456789UL, 987654321UL, 1009UL)] + public void MulMod_matches_BigInteger(ulong a, ulong b, ulong modulus) + { + UInt128 ua = a % modulus; + UInt128 ub = b % modulus; + UInt128 mod = modulus; - UInt128 expected = (UInt128)(((BigInteger)ua * (BigInteger)ub) % modulus); + UInt128 expected = (UInt128)(((BigInteger)ua * (BigInteger)ub) % modulus); - ua.MulMod(ub, mod).Should().Be(expected); - } + ua.MulMod(ub, mod).Should().Be(expected); + } - [Fact] - [Trait("Category", "Fast")] - public void MulMod_handles_large_inputs() - { - UInt128 a = (UInt128.One << 80) + 12345; - UInt128 b = (UInt128.One << 96) + 67890; - UInt128 mod = (UInt128.One << 100) - 93; + [Fact] + [Trait("Category", "Fast")] + public void MulMod_handles_large_inputs() + { + UInt128 a = (UInt128.One << 80) + 12345; + UInt128 b = (UInt128.One << 96) + 67890; + UInt128 mod = (UInt128.One << 100) - 93; - UInt128 expected = (UInt128)(((BigInteger)a * (BigInteger)b) % (BigInteger)mod); + UInt128 expected = (UInt128)(((BigInteger)a * (BigInteger)b) % (BigInteger)mod); - a.MulMod(b, mod).Should().Be(expected); - } + a.MulMod(b, mod).Should().Be(expected); + } } diff --git a/PerfectNumbers.Core.Tests/ULongExtensionsTests.cs b/PerfectNumbers.Core.Tests/ULongExtensionsTests.cs index 1cbee47..84c433c 100644 --- a/PerfectNumbers.Core.Tests/ULongExtensionsTests.cs +++ b/PerfectNumbers.Core.Tests/ULongExtensionsTests.cs @@ -1,225 +1,250 @@ using System.Numerics; using FluentAssertions; +using PerfectNumbers.Core.Gpu.Accelerators; using Xunit; namespace PerfectNumbers.Core.Tests; public class ULongExtensionsTests { - [Theory] - [Trait("Category", "Fast")] - [InlineData(0UL)] - [InlineData(1UL)] - [InlineData(9UL)] - [InlineData(10UL)] - [InlineData(1234567890UL)] - [InlineData(ulong.MaxValue)] - public void Mod10_returns_same_as_operator(ulong value) - { - ulong expected = value % 10UL; + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(1UL)] + [InlineData(9UL)] + [InlineData(10UL)] + [InlineData(1234567890UL)] + [InlineData(ulong.MaxValue)] + public void Mod10_returns_same_as_operator(ulong value) + { + ulong expected = value % 10UL; - ULongExtensions.Mod10(value).Should().Be(expected); - } + ULongExtensions.Mod10(value).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(0UL)] - [InlineData(ulong.MaxValue)] - [InlineData(1234567890123456789UL)] - public void Mod128_matches_operator(ulong value) - { - value.Mod128().Should().Be(value % 128UL); - } + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL)] + [InlineData(ulong.MaxValue)] + [InlineData(1234567890123456789UL)] + public void Mod128_matches_operator(ulong value) + { + value.Mod128().Should().Be(value % 128UL); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(1UL)] - [InlineData(123456789UL)] - [InlineData(ulong.MaxValue)] - public void Mod10_8_5_3Steps_matches_manual_formula(ulong value) - { - value.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); + [Theory] + [Trait("Category", "Fast")] + [InlineData(1UL)] + [InlineData(123456789UL)] + [InlineData(ulong.MaxValue)] + public void Mod10_8_5_3Steps_matches_manual_formula(ulong value) + { + value.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); - ulong expected10 = ((value % 10UL) << 1) % 10UL; - ulong expected8 = ((value & 7UL) << 1) & 7UL; - ulong expected5 = ((value % 5UL) << 1) % 5UL; - ulong expected3 = ((value % 3UL) << 1) % 3UL; + ulong expected10 = ((value % 10UL) << 1) % 10UL; + ulong expected8 = ((value & 7UL) << 1) & 7UL; + ulong expected5 = ((value % 5UL) << 1) % 5UL; + ulong expected3 = ((value % 3UL) << 1) % 3UL; - step10.Should().Be(expected10); - step8.Should().Be(expected8); - step5.Should().Be(expected5); - step3.Should().Be(expected3); - } + step10.Should().Be(expected10); + step8.Should().Be(expected8); + step5.Should().Be(expected5); + step3.Should().Be(expected3); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(2UL, true)] - [InlineData(9UL, false)] - [InlineData(97UL, true)] - public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected) - { - candidate.IsPrimeCandidate().Should().Be(expected); - } + [Theory] + [Trait("Category", "Fast")] + [InlineData(2UL, true)] + [InlineData(9UL, false)] + [InlineData(97UL, true)] + public void IsPrimeCandidate_filters_composites(ulong candidate, bool expected) + { + candidate.IsPrimeCandidate().Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(3UL)] - [InlineData(5UL)] - [InlineData(7UL)] - [InlineData(11UL)] - public void CalculateOrder_matches_naive_algorithm(ulong q) - { - ulong expected = NaiveOrder(q); + [Theory] + [Trait("Category", "Fast")] + [InlineData(3UL)] + [InlineData(5UL)] + [InlineData(7UL)] + [InlineData(11UL)] + public void CalculateOrder_matches_naive_algorithm(ulong q) + { + ulong expected = NaiveOrder(q); - q.CalculateOrder().Should().Be(expected); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + q.CalculateOrder(gpu).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(2UL, 10UL, 17UL)] - [InlineData(123456789UL, 3UL, 97UL)] - public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus) - { - ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus); + [Theory] + [Trait("Category", "Fast")] + [InlineData(2UL, 10UL, 17UL)] + [InlineData(123456789UL, 3UL, 97UL)] + public void ModPow64_matches_BigInteger(ulong value, ulong exponent, ulong modulus) + { + ulong expected = (ulong)BigInteger.ModPow(value, exponent, modulus); - value.ModPow64(exponent, modulus).Should().Be(expected); - } + value.ModPow64(exponent, modulus).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(10UL, 17UL)] - [InlineData(123UL, 97UL)] - public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus) - { - UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus); + [Theory] + [Trait("Category", "Fast")] + [InlineData(10UL, 17UL)] + [InlineData(123UL, 97UL)] + public void PowMod_returns_same_as_BigInteger(ulong exponent, ulong modulus) + { + UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus); - exponent.PowMod((UInt128)modulus).Should().Be(expected); - } + exponent.PowMod((UInt128)modulus).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(5UL, 7UL, 3UL, 4UL)] - [InlineData(10UL, 7UL, 3UL, 2UL)] - public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected) - { - ((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected); - } + [Theory] + [Trait("Category", "Fast")] + [InlineData(5UL, 7UL, 3UL, 4UL)] + [InlineData(10UL, 7UL, 3UL, 2UL)] + public void PowModWithCycle_uses_cycle_length(ulong exponent, ulong modulus, ulong cycle, ulong expected) + { + ((UInt128)exponent).PowModWithCycle((UInt128)modulus, cycle).Should().Be((UInt128)expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(15UL, true)] - [InlineData(7UL, false)] - [InlineData(9UL, false)] - public void SharesFactorWithExponentMinusOne_detects_relationship(ulong exponent, bool expected) - { - exponent.SharesFactorWithExponentMinusOne().Should().Be(expected); - } + [Theory] + [Trait("Category", "Fast")] + [InlineData(15UL, true)] + [InlineData(7UL, false)] + [InlineData(9UL, false)] + public void SharesFactorWithExponentMinusOne_detects_relationship(ulong exponent, bool expected) + { + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + exponent.SharesFactorWithExponentMinusOne(gpu).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(1234567890123456789UL, 3UL)] - [InlineData(ulong.MaxValue, 7UL)] - public void FastDiv64_matches_division(ulong value, ulong divisor) - { - ulong mul = (ulong)(((UInt128)1 << 64) / divisor); + [Theory] + [Trait("Category", "Fast")] + [InlineData(1234567890123456789UL, 3UL)] + [InlineData(ulong.MaxValue, 7UL)] + public void FastDiv64_matches_division(ulong value, ulong divisor) + { + ulong mul = (ulong)(((UInt128)1 << 64) / divisor); - value.FastDiv64(divisor, mul).Should().Be(value / divisor); - } + value.FastDiv64(divisor, mul).Should().Be(value / divisor); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(0UL, 0UL)] - [InlineData(ulong.MaxValue, 1UL)] - [InlineData(123456789UL, 987654321UL)] - public void MulHighCpu_matches_high_bits(ulong x, ulong y) - { - ulong expected = (ulong)(((UInt128)x * y) >> 64); + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL, 0UL)] + [InlineData(ulong.MaxValue, 1UL)] + [InlineData(123456789UL, 987654321UL)] + public void MulHighCpu_matches_high_bits(ulong x, ulong y) + { + ulong expected = (ulong)(((UInt128)x * y) >> 64); - x.MulHighCpu(y).Should().Be(expected); - } + x.MulHighCpu(y).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(0UL, 0UL)] - [InlineData(ulong.MaxValue, 1UL)] - [InlineData(123456789UL, 987654321UL)] - public void MulHighGpu_matches_cpu_baseline(ulong x, ulong y) - { - x.MulHighGpu(y).Should().Be(x.MulHighCpu(y)); - } + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL, 0UL)] + [InlineData(ulong.MaxValue, 1UL)] + [InlineData(123456789UL, 987654321UL)] + public void MulHighGpu_matches_cpu_baseline(ulong x, ulong y) + { + x.MulHighGpu(y).Should().Be(x.MulHighCpu(y)); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(0UL, 0UL)] - [InlineData(ulong.MaxValue, 2UL)] - [InlineData(123456789UL, 987654321UL)] - public void Mul64_matches_UInt128_product(ulong a, ulong b) - { - UInt128 expected = (UInt128)a * b; + [Theory] + [Trait("Category", "Fast")] + [InlineData(0UL, 0UL)] + [InlineData(ulong.MaxValue, 2UL)] + [InlineData(123456789UL, 987654321UL)] + public void Mul64_matches_UInt128_product(ulong a, ulong b) + { + UInt128 expected = (UInt128)a * b; - a.Mul64(b).Should().Be(expected); - } + a.Mul64(b).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(123456789UL, 987654321UL, 97UL)] - [InlineData(ulong.MaxValue, ulong.MaxValue, 1009UL)] - public void MulMod64_matches_BigInteger(ulong a, ulong b, ulong modulus) - { - ulong expected = (ulong)BigInteger.Remainder((BigInteger)a * b, modulus); + [Theory] + [Trait("Category", "Fast")] + [InlineData(123456789UL, 987654321UL, 97UL)] + [InlineData(ulong.MaxValue, ulong.MaxValue, 1009UL)] + public void MulMod64_matches_BigInteger(ulong a, ulong b, ulong modulus) + { + ulong expected = (ulong)BigInteger.Remainder((BigInteger)a * b, modulus); - a.MulMod64(b, modulus).Should().Be(expected); - } + a.MulMod64(b, modulus).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(100UL, 7UL, 3UL)] - [InlineData(123UL, 9UL, 6UL)] - public void PowModWithCycle_ulong_overload_uses_cycle(ulong exponent, ulong modulus, ulong cycle) - { - UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus); + [Theory] + [Trait("Category", "Fast")] + [InlineData(100UL, 7UL, 3UL)] + [InlineData(123UL, 9UL, 6UL)] + public void PowModWithCycle_ulong_overload_uses_cycle(ulong exponent, ulong modulus, ulong cycle) + { + UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus); - exponent.PowModWithCycle((UInt128)modulus, cycle).Should().Be(expected); - } + exponent.PowModWithCycle((UInt128)modulus, cycle).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(100UL, 7UL, 3UL)] - [InlineData(123UL, 9UL, 6UL)] - public void PowModWithCycle_ulong_uint128_cycle_uses_cycle(ulong exponent, ulong modulus, ulong cycle) - { - UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus); + [Theory] + [Trait("Category", "Fast")] + [InlineData(100UL, 7UL, 3UL)] + [InlineData(123UL, 9UL, 6UL)] + public void PowModWithCycle_ulong_uint128_cycle_uses_cycle(ulong exponent, ulong modulus, ulong cycle) + { + UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus); - exponent.PowModWithCycle((UInt128)modulus, (UInt128)cycle).Should().Be(expected); - } + exponent.PowModWithCycle((UInt128)modulus, (UInt128)cycle).Should().Be(expected); + } - [Theory] - [Trait("Category", "Fast")] - [InlineData(11UL, 13UL)] - public void PowModCrt_matches_BigInteger(ulong exponent, ulong modulus) - { - MersenneDivisorCycles cycles = MersenneDivisorCycles.Shared; - UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus); + [Theory] + [Trait("Category", "Fast")] + [InlineData(11UL, 13UL)] + public void PowModCrt_matches_BigInteger(ulong exponent, ulong modulus) + { + MersenneDivisorCycles cycles = MersenneDivisorCycles.Shared; + UInt128 expected = (UInt128)BigInteger.ModPow(2, exponent, modulus); - exponent.PowModCrt((UInt128)modulus, cycles).Should().Be(expected); - } + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + try + { + exponent.PowModCrt(gpu, (UInt128)modulus, cycles).Should().Be(expected); + } + finally + { + PrimeOrderCalculatorAccelerator.Return(gpu); + } + } - private static ulong NaiveOrder(ulong q) - { - if (q <= 2UL) - { - return 0UL; - } + private static ulong NaiveOrder(ulong q) + { + if (q <= 2UL) + { + return 0UL; + } - ulong order = 1UL; - ulong value = 2UL % q; - while (value != 1UL) - { - value = (value << 1) % q; - order++; - } + ulong order = 1UL; + ulong value = 2UL % q; + while (value != 1UL) + { + value = (value << 1) % q; + order++; + } - return order; - } + return order; + } } diff --git a/PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs b/PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs index 2d36028..3d4d439 100644 --- a/PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs +++ b/PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs @@ -1,11 +1,14 @@ +using PerfectNumbers.Core.Gpu.Accelerators; + namespace PerfectNumbers.Core.Cpu; -internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession +internal sealed class MersenneCpuDivisorScanSession(PrimeOrderCalculatorAccelerator gpu) : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession { - public void Reset() + public void Reset() { } + // TODO: Convert this to static method public void CheckDivisor( ulong divisor, in MontgomeryDivisorData divisorData, @@ -29,7 +32,7 @@ internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDi if (divisorCycle == 0UL) { - divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor); + divisorCycle = DivisorCycleCache.Shared.GetCycleLength(gpu, divisor); if (divisorCycle == 0UL) { // DivisorCycleCache guarantees a positive cycle for divisors greater than one. diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index 5b3ba63..a3e3f62 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -4,7 +4,7 @@ using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core.Cpu; -public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester +public sealed class MersenneNumberDivisorByDivisorCpuTester() : IMersenneNumberDivisorByDivisorTester { private ulong _divisorLimit; private int _batchSize = 1_024; @@ -42,7 +42,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit); - public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) + public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor) { ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); @@ -58,6 +58,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv bool processedAll; bool composite = CheckDivisors( + gpu, prime, allowedMax, out processedAll, @@ -84,7 +85,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } } - public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() + public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu) { MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession(); if (session is not null) @@ -92,16 +93,16 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv return session; } - return new MersenneCpuDivisorScanSession(); + return new MersenneCpuDivisorScanSession(gpu); } private bool CheckDivisors( + PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong allowedMax, out bool processedAll, out ulong foundDivisor) { - foundDivisor = 0UL; // The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs. // Keeping the guard commented out documents the reasoning for benchmarks and tests. // if (allowedMax < 3UL) @@ -171,6 +172,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv remainder11 = (byte)(divisorLow % 11UL); return CheckDivisors64( + gpu, prime, stepLow, limit.Low, @@ -203,7 +205,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL); remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL); - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); var divisorPool = MontgomeryDivisorDataPool.Shared; while (divisor.CompareTo(limit) <= 0) { @@ -225,6 +226,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv // Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles // for the exponents scanned here, so skip the unused cache fallback and compute directly. divisorCycle = MersenneDivisorCycles.CalculateCycleLength( + gpu, candidate, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); @@ -241,7 +243,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv // the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p. foundDivisor = candidate; processedAll = true; - PrimeOrderCalculatorAccelerator.Return(gpu); return true; } @@ -259,13 +260,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv remainder11 = AddMod11(remainder11, step11); } - PrimeOrderCalculatorAccelerator.Return(gpu); processedAll = true; foundDivisor = 0UL; return false; } private static bool CheckDivisors64( + PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong step, ulong limit, @@ -285,7 +286,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv out ulong foundDivisor) { bool canAdvance = step <= limit; - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); Queue divisorPool = MontgomeryDivisorDataPool.Shared; if (!canAdvance) { @@ -304,6 +304,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv out bool primeOrderFailed) || computedCycle == 0UL) { divisorCycle = MersenneDivisorCycles.CalculateCycleLength( + gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); @@ -319,7 +320,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { foundDivisor = divisor; processedAll = true; - PrimeOrderCalculatorAccelerator.Return(gpu); return true; } @@ -332,7 +332,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv processedAll = true; foundDivisor = 0UL; - PrimeOrderCalculatorAccelerator.Return(gpu); return false; } @@ -340,7 +339,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { processedAll = true; foundDivisor = 0UL; - PrimeOrderCalculatorAccelerator.Return(gpu); return false; } @@ -361,6 +359,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv out bool primeOrderFailed) || computedCycle == 0UL) { divisorCycle = MersenneDivisorCycles.CalculateCycleLength( + gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); @@ -375,7 +374,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { foundDivisor = divisor; processedAll = true; - PrimeOrderCalculatorAccelerator.Return(gpu); return true; } @@ -390,7 +388,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { processedAll = true; foundDivisor = 0UL; - PrimeOrderCalculatorAccelerator.Return(gpu); return false; } diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberIncrementalCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberIncrementalCpuTester.cs index c794e07..fecd156 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberIncrementalCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberIncrementalCpuTester.cs @@ -1,90 +1,92 @@ +using PerfectNumbers.Core.Gpu.Accelerators; + namespace PerfectNumbers.Core.Cpu; public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType) { - private static readonly MersenneDivisorCycles _cycles = MersenneDivisorCycles.Shared; - private readonly GpuKernelType _kernelType = kernelType; + private static readonly MersenneDivisorCycles _cycles = MersenneDivisorCycles.Shared; + private readonly GpuKernelType _kernelType = kernelType; - public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) - { - UInt128 k = UInt128.One; - UInt128 one = k; - ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; - // Use residue automaton always on CPU - var auto = new MersenneResidueAutomaton(exponent); + public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) + { + UInt128 k = UInt128.One; + UInt128 one = k; + ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; + // Use residue automaton always on CPU + var auto = new MersenneResidueAutomaton(exponent); - bool reject; - bool shouldCheck; - UInt128 q = auto.CurrentQ(); - UInt128 divPow; - UInt128 halfPow; - UInt128 phi; - UInt128 qCycle; + bool reject; + bool shouldCheck; + UInt128 q = auto.CurrentQ(); + UInt128 divPow; + UInt128 halfPow; + UInt128 phi; + UInt128 qCycle; - ulong div; - ulong mod8; - ulong phi64; + ulong div; + ulong mod8; + ulong phi64; - while (k <= maxK && Volatile.Read(ref isPrime)) - { - // TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block - // and compute missing cycles on the configured device without queuing additional block generation. - qCycle = MersenneDivisorCycles.GetCycle(q); - shouldCheck = auto.Mod10R != 5UL; - if (shouldCheck) - { - mod8 = auto.Mod8R; - if (mod8 != 1UL && mod8 != 7UL) - { - shouldCheck = false; - } - else if (auto.Mod3R == 0UL || auto.Mod5R == 0UL) - { - // TODO: Swap these Mod3/Mod5 zero checks to the cached residue tables once the automaton exposes - // the benchmarked lookup-based helpers so CPU scans avoid runtime modulo instructions. - shouldCheck = false; - } - } + while (k <= maxK && Volatile.Read(ref isPrime)) + { + // TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block + // and compute missing cycles on the configured device without queuing additional block generation. + qCycle = MersenneDivisorCycles.GetCycle(gpu, q); + shouldCheck = auto.Mod10R != 5UL; + if (shouldCheck) + { + mod8 = auto.Mod8R; + if (mod8 != 1UL && mod8 != 7UL) + { + shouldCheck = false; + } + else if (auto.Mod3R == 0UL || auto.Mod5R == 0UL) + { + // TODO: Swap these Mod3/Mod5 zero checks to the cached residue tables once the automaton exposes + // the benchmarked lookup-based helpers so CPU scans avoid runtime modulo instructions. + shouldCheck = false; + } + } - if (shouldCheck) - { - if (_kernelType == GpuKernelType.Pow2Mod) - { - reject = exponent.PowModWithCycle(q, qCycle) == UInt128.One; - } - else - { - phi = q - one; - reject = false; - if (phi <= ulong.MaxValue) - { - phi64 = (ulong)phi; - if (phi64.PowModWithCycle(q, qCycle) == one) - { - halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - one; - if (halfPow.BinaryGcd(q) == one) - { - div = phi64.FastDiv64(exponent, divMul); - divPow = div.PowModWithCycle(q, qCycle) - one; - if (divPow.BinaryGcd(q) == one) - { - reject = true; - } - } - } - } - } + if (shouldCheck) + { + if (_kernelType == GpuKernelType.Pow2Mod) + { + reject = exponent.PowModWithCycle(q, qCycle) == UInt128.One; + } + else + { + phi = q - one; + reject = false; + if (phi <= ulong.MaxValue) + { + phi64 = (ulong)phi; + if (phi64.PowModWithCycle(q, qCycle) == one) + { + halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - one; + if (halfPow.BinaryGcd(q) == one) + { + div = phi64.FastDiv64(exponent, divMul); + divPow = div.PowModWithCycle(q, qCycle) - one; + if (divPow.BinaryGcd(q) == one) + { + reject = true; + } + } + } + } + } - if (reject && q.IsPrimeCandidate()) - { - Volatile.Write(ref isPrime, false); - break; - } - } + if (reject && q.IsPrimeCandidate()) + { + Volatile.Write(ref isPrime, false); + break; + } + } - k += 1UL; - auto.Next(); - q = auto.CurrentQ(); - } - } + k += 1UL; + auto.Next(); + q = auto.CurrentQ(); + } + } } diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberLucasLehmerCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberLucasLehmerCpuTester.cs index d9283bb..9440643 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberLucasLehmerCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberLucasLehmerCpuTester.cs @@ -1,44 +1,46 @@ +using PerfectNumbers.Core.Gpu.Accelerators; + namespace PerfectNumbers.Core; public class MersenneNumberLucasLehmerCpuTester { - public bool IsPrime(ulong exponent) - { - // Early rejections aligned with incremental/order sieves, but safe for small p: - // - If 3 | p and p != 3, then 7 | M_p -> composite. - // - If p ≡ 1 (mod 4) and p shares a factor with (p-1), reject fast. - // - If p is divisible by divisors specific to numbers ending with 1 or 7, reject fast. - // TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering - // shares the benchmarked bitmask implementations and avoids slow modulo instructions in the hot path. - if ( - ((exponent % 3UL) == 0UL && exponent != 3UL) || - ((exponent % 5UL) == 0UL && exponent != 5UL) || - ((exponent % 7UL) == 0UL && exponent != 7UL) || - ((exponent % 11UL) == 0UL && exponent != 11UL) - ) - { - return false; - } + public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent) + { + // Early rejections aligned with incremental/order sieves, but safe for small p: + // - If 3 | p and p != 3, then 7 | M_p -> composite. + // - If p ≡ 1 (mod 4) and p shares a factor with (p-1), reject fast. + // - If p is divisible by divisors specific to numbers ending with 1 or 7, reject fast. + // TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering + // shares the benchmarked bitmask implementations and avoids slow modulo instructions in the hot path. + if ( + ((exponent % 3UL) == 0UL && exponent != 3UL) || + ((exponent % 5UL) == 0UL && exponent != 5UL) || + ((exponent % 7UL) == 0UL && exponent != 7UL) || + ((exponent % 11UL) == 0UL && exponent != 11UL) + ) + { + return false; + } - if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne()) - { - return false; - } + if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu)) + { + return false; + } - UInt128 s = UInt128Numbers.Four, two = UInt128Numbers.Two, - m = (UInt128.One << (int)exponent) - UInt128.One; + UInt128 s = UInt128Numbers.Four, two = UInt128Numbers.Two, + m = (UInt128.One << (int)exponent) - UInt128.One; - UInt128 limit = exponent - UInt128Numbers.Two; - for (UInt128 i = UInt128.Zero; i < limit; i++) - { - // Lucas–Lehmer squares the evolving residue, so we stay on the Montgomery powmod path rather than the pow2-specific helper. - s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(m)) - two; - if (s < UInt128.Zero) - { - s += m; - } - } + UInt128 limit = exponent - UInt128Numbers.Two; + for (UInt128 i = UInt128.Zero; i < limit; i++) + { + // Lucas–Lehmer squares the evolving residue, so we stay on the Montgomery powmod path rather than the pow2-specific helper. + s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycle(gpu, m)) - two; + if (s < UInt128.Zero) + { + s += m; + } + } - return s == UInt128.Zero; - } + return s == UInt128.Zero; + } } diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberOrderCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberOrderCpuTester.cs index 14c3638..1a8f64b 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberOrderCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberOrderCpuTester.cs @@ -1,10 +1,12 @@ +using PerfectNumbers.Core.Gpu.Accelerators; + namespace PerfectNumbers.Core.Cpu; public class MersenneNumberOrderCpuTester(GpuKernelType kernelType) { private readonly GpuKernelType _kernelType = kernelType; - public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) + public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { UInt128 k = 1UL; var auto = new MersenneResidueAutomaton(exponent); @@ -13,10 +15,10 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType) while (k <= maxK && Volatile.Read(ref isPrime)) { - qCycle = MersenneDivisorCycles.GetCycle(q); - // TODO: When this lookup misses the snapshot, invoke the configured device to compute the - // single required cycle on demand without persisting it so the CPU order path retains the - // cycle-stepping speedups while honoring the no-extra-cache constraint for large divisors. + qCycle = MersenneDivisorCycles.GetCycle(gpu, q); + // TODO: When this lookup misses the snapshot, invoke the configured device to compute the + // single required cycle on demand without persisting it so the CPU order path retains the + // cycle-stepping speedups while honoring the no-extra-cache constraint for large divisors. int allowMask2 = lastDigit == LastDigit.Seven ? CpuConstants.LastSevenMask10 : CpuConstants.LastOneMask10; bool shouldCheck = ((allowMask2 >> (int)auto.Mod10R) & 1) != 0; if (shouldCheck) @@ -33,33 +35,33 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType) } if (shouldCheck) { - if (_kernelType == GpuKernelType.Pow2Mod) - { - if (exponent.PowModWithCycle(q, qCycle) == 1UL) - { - Volatile.Write(ref isPrime, false); - break; - } - } - else - { - UInt128 phi = q - 1UL; - if (phi <= ulong.MaxValue) - { - ulong phi64 = (ulong)phi; - if (phi64.PowModWithCycle(q, qCycle) == 1UL) - { - UInt128 halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - 1UL; - if (halfPow.BinaryGcd(q) == 1UL) - { - ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; - ulong div = phi64.FastDiv64(exponent, divMul); - UInt128 divPow = div.PowModWithCycle(q, qCycle) - 1UL; - if (divPow.BinaryGcd(q) == 1UL) - { - Volatile.Write(ref isPrime, false); - break; - } + if (_kernelType == GpuKernelType.Pow2Mod) + { + if (exponent.PowModWithCycle(q, qCycle) == 1UL) + { + Volatile.Write(ref isPrime, false); + break; + } + } + else + { + UInt128 phi = q - 1UL; + if (phi <= ulong.MaxValue) + { + ulong phi64 = (ulong)phi; + if (phi64.PowModWithCycle(q, qCycle) == 1UL) + { + UInt128 halfPow = (phi64 >> 1).PowModWithCycle(q, qCycle) - 1UL; + if (halfPow.BinaryGcd(q) == 1UL) + { + ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; + ulong div = phi64.FastDiv64(exponent, divMul); + UInt128 divPow = div.PowModWithCycle(q, qCycle) - 1UL; + if (divPow.BinaryGcd(q) == 1UL) + { + Volatile.Write(ref isPrime, false); + break; + } } } } @@ -70,7 +72,7 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType) auto.Next(); q = auto.CurrentQ(); } - + return; } diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs index fd19c49..380d115 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberResidueCpuTester.cs @@ -1,3 +1,5 @@ +using PerfectNumbers.Core.Gpu.Accelerators; + namespace PerfectNumbers.Core.Cpu; public class MersenneNumberResidueCpuTester @@ -5,7 +7,7 @@ public class MersenneNumberResidueCpuTester private ModResidueTracker? _mersenneResidueTracker; // CPU residue variant using tracker + unrolled residue updates (no method calls in the loop). - public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) + public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) { // Initialize/update Mersenne residue tracker and start a merge walk for ascending q divisors @@ -92,7 +94,7 @@ public class MersenneNumberResidueCpuTester // (and below) so every qualifying q reuses precomputed lengths instead of recomputing via // tracker.MergeOrAppend. // cycle-based quick check for small q - if (localIsPrime && tracker.MergeOrAppend(exponent, q, out isDivider) && isDivider && + if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, q, out isDivider) && isDivider && (q <= ulong.MaxValue ? ((ulong)q).IsPrimeCandidate() : q.IsPrimeCandidate())) { localIsPrime = false; @@ -105,7 +107,7 @@ public class MersenneNumberResidueCpuTester ((allowMask >> (int)r10_1) & 1) != 0 && (r8_1 == 1UL || r8_1 == 7UL) && r3_1 != 0UL && r5_1 != 0UL && localIsPrime) { - if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider && + if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider && (qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate())) { localIsPrime = false; @@ -118,7 +120,7 @@ public class MersenneNumberResidueCpuTester ((allowMask >> (int)r10_2) & 1) != 0 && (r8_2 == 1UL || r8_2 == 7UL) && r3_2 != 0UL && r5_2 != 0UL && localIsPrime) { - if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider && + if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider && (qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate())) { localIsPrime = false; @@ -131,7 +133,7 @@ public class MersenneNumberResidueCpuTester ((allowMask >> (int)r10_3) & 1) != 0 && (r8_3 == 1UL || r8_3 == 7UL) && r3_3 != 0UL && r5_3 != 0UL && localIsPrime) { - if (localIsPrime && tracker.MergeOrAppend(exponent, qIncremental, out isDivider) && isDivider && + if (localIsPrime && tracker.MergeOrAppend(gpu, exponent, qIncremental, out isDivider) && isDivider && (qIncremental <= ulong.MaxValue ? ((ulong)qIncremental).IsPrimeCandidate() : qIncremental.IsPrimeCandidate())) { localIsPrime = false; diff --git a/PerfectNumbers.Core/DivisorCycleCache.cs b/PerfectNumbers.Core/DivisorCycleCache.cs index 3f8f571..00bf481 100644 --- a/PerfectNumbers.Core/DivisorCycleCache.cs +++ b/PerfectNumbers.Core/DivisorCycleCache.cs @@ -57,13 +57,13 @@ public sealed class DivisorCycleCache _snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); } - public ulong GetCycleLength(ulong divisor, bool skipPrimeOrderHeuristic = false) + public ulong GetCycleLength(PrimeOrderCalculatorAccelerator gpu, ulong divisor, bool skipPrimeOrderHeuristic = false) { if (!skipPrimeOrderHeuristic) { Span result = stackalloc ulong[1]; ReadOnlySpan singleDivisor = stackalloc ulong[1] { divisor }; - GetCycleLengths(singleDivisor, result); + GetCycleLengths(gpu, singleDivisor, result); return result[0]; } @@ -90,12 +90,12 @@ public sealed class DivisorCycleCache Queue divisorPool = MontgomeryDivisorDataPool.Shared; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor); - cycleLength = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: true); + cycleLength = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true); divisorPool.Return(divisorData); return cycleLength; } - public void GetCycleLengths(ReadOnlySpan divisors, Span cycles) + public void GetCycleLengths(PrimeOrderCalculatorAccelerator gpu,ReadOnlySpan divisors, Span cycles) { ulong[] snapshot = _snapshot; int length = divisors.Length; @@ -139,7 +139,7 @@ public sealed class DivisorCycleCache } else { - ComputeCyclesCpu(divisors, cycles, missingIndices); + ComputeCyclesCpu(gpu, divisors, cycles, missingIndices); } } @@ -187,7 +187,7 @@ public sealed class DivisorCycleCache } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ComputeCyclesCpu(ReadOnlySpan divisors, Span cycles, ReadOnlySpan indices) + private static void ComputeCyclesCpu(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan divisors, Span cycles, ReadOnlySpan indices) { Queue divisorPool = MontgomeryDivisorDataPool.Shared; for (int i = 0; i < indices.Length; i++) @@ -195,7 +195,7 @@ public sealed class DivisorCycleCache int targetIndex = indices[i]; ulong divisor = divisors[targetIndex]; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor); - cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData); + cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData); divisorPool.Return(divisorData); } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 0eb7cf1..5b2590c 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -20,7 +20,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN private ulong _divisorLimit; // private bool _isConfigured; - private readonly ConcurrentBag[] _resourcePool = [..AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentBag())]; + private readonly ConcurrentBag[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentBag())]; private readonly ConcurrentBag _sessionPool = []; @@ -50,9 +50,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN // _isConfigured = true; } - private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; - - public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) + public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor) { ulong allowedMax; int batchCapacity; @@ -87,7 +85,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN // GpuPrimeWorkLimiter(); // int acceleratorIndex = AcceleratorPool.Shared.Rent(); // var accelerator = _accelerators[acceleratorIndex]; - var gpu = PrimeOrderCalculatorAccelerator.Rent(batchCapacity); var acceleratorIndex = gpu.AcceleratorIndex; // Monitor.Enter(gpuLease.ExecutionLock); @@ -117,7 +114,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN out processedCount); ReturnBatchResources(acceleratorIndex, resources); - PrimeOrderCalculatorAccelerator.Return(gpu); // Monitor.Exit(gpuLease.ExecutionLock); // GpuPrimeWorkLimiter.Release(); @@ -364,7 +360,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN { if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) { - return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); + return MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); } return computedCycle; @@ -481,8 +477,8 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession { private readonly MersenneNumberDivisorByDivisorGpuTester _owner; - private readonly PrimeOrderCalculatorAccelerator _primeTesterAccelerator; private readonly Accelerator _accelerator; + private readonly PrimeOrderCalculatorAccelerator _primeOrderCalculatorAccelerator; private readonly AcceleratorStream _stream; private Action, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView> _kernel = null!; private MemoryBuffer1D _divisorBuffer = null!; @@ -496,15 +492,36 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN private int _capacity; - internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) + internal DivisorScanSession(PrimeOrderCalculatorAccelerator gpu, MersenneNumberDivisorByDivisorGpuTester owner) { _owner = owner; // GpuPrimeWorkLimiter.Acquire(); - _primeTesterAccelerator = PrimeOrderCalculatorAccelerator.Rent(1); - Accelerator accelerator = _primeTesterAccelerator.Accelerator; + _primeOrderCalculatorAccelerator = gpu; + Accelerator accelerator = gpu.Accelerator; _accelerator = accelerator; _stream = accelerator.CreateStream(); _capacity = Math.Max(1, owner._gpuBatchSize); + + // lock (accelerator) + { + _divisorBuffer = accelerator.Allocate1D(1); + _offsetBuffer = accelerator.Allocate1D(1); + _countBuffer = accelerator.Allocate1D(1); + _cycleBuffer = accelerator.Allocate1D(1); + _firstHitBuffer = accelerator.Allocate1D(1); + } + + int desiredCapacity = PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount; + _capacity = desiredCapacity; + // lock (accelerator) + { + _exponentsBuffer = accelerator.Allocate1D(desiredCapacity); + _hitBuffer = accelerator.Allocate1D(desiredCapacity); + } + + _hostBuffer = ThreadStaticPools.UlongPool.Rent(desiredCapacity); + + _kernel = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(DivisorByDivisorKernels.CheckKernel)).CreateLauncherDelegate, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>>(); } internal void Reset() @@ -513,45 +530,16 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN private void EnsureExecutionResourcesLocked(int requiredCapacity) { - Accelerator accelerator = _accelerator; - - if (_kernel == null) + if (requiredCapacity > _capacity) { - _kernel = KernelUtil.GetKernel(_accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(DivisorByDivisorKernels.CheckKernel)).CreateLauncherDelegate, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>>(); - } + _exponentsBuffer.Dispose(); + _hitBuffer.Dispose(); + ThreadStaticPools.UlongPool.Return(_hostBuffer, clearArray: false); - if (_divisorBuffer == null) - { - // lock (accelerator) - { - _divisorBuffer = accelerator.Allocate1D(1); - _offsetBuffer = accelerator.Allocate1D(1); - _countBuffer = accelerator.Allocate1D(1); - _cycleBuffer = accelerator.Allocate1D(1); - _firstHitBuffer = accelerator.Allocate1D(1); - } - } - - bool allocateBuffers = _exponentsBuffer == null; - if (!allocateBuffers && _capacity < requiredCapacity) - { - _exponentsBuffer?.Dispose(); - _hitBuffer?.Dispose(); - if (_hostBuffer != null) - { - ThreadStaticPools.UlongPool.Return(_hostBuffer, clearArray: false); - _hostBuffer = null!; - } - - _exponentsBuffer = null!; - _hitBuffer = null!; - allocateBuffers = true; - } - - if (allocateBuffers) - { + Accelerator accelerator = _accelerator; int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity; _capacity = desiredCapacity; + // lock (accelerator) { _exponentsBuffer = accelerator.Allocate1D(desiredCapacity); @@ -581,13 +569,12 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN ulong cycle = divisorCycle; ulong firstPrime = primes[0]; - + var gpu = _primeOrderCalculatorAccelerator; if (cycle == 0UL) { - var gpu = _primeTesterAccelerator; if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) { - cycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); + cycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); } else { @@ -714,7 +701,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); } - public DivisorScanSession CreateDivisorSession() + public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu) { // The tester is configured once at startup and the session pool relies on thread-safe collections, so the previous synchronization guard stays commented out. // lock (_sync) @@ -739,8 +726,6 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN return session; } - return new DivisorScanSession(this); + return new DivisorScanSession(gpu, this); } - - IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs index 574df81..ff187d0 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorGpuTester.cs @@ -1,5 +1,6 @@ using ILGPU; using ILGPU.Runtime; +using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core.Gpu; @@ -86,7 +87,7 @@ public sealed class MersenneNumberDivisorGpuTester private static (ulong divisor, uint cycle)[]? _divisorCandidates = []; - public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted) + public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted) { ReadOnlyGpuUInt128 readOnlyDivisor; @@ -142,7 +143,7 @@ public sealed class MersenneNumberDivisorGpuTester continue; } - UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d); + UInt128 cycle128 = MersenneDivisorCycles.GetCycle(gpu, d); if ((UInt128)p % cycle128 != UInt128.Zero) { continue; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs index 9d64c3b..09d7aee 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs @@ -2,6 +2,7 @@ using System.Buffers; using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; +using PerfectNumbers.Core.Gpu.Accelerators; using UInt128 = System.UInt128; namespace PerfectNumbers.Core.Gpu; @@ -131,7 +132,7 @@ public class MersenneNumberLucasLehmerGpuTester // Configurable LL slice size to keep kernels short. Default 32. public int SliceSize = 32; - public bool IsPrime(ulong exponent, bool runOnGpu) + public bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent, bool runOnGpu) { // Early rejections aligned with incremental/order sieves, but safe for small p: // - If 3 | p and p != 3, then 7 | M_p -> composite. @@ -142,7 +143,7 @@ public class MersenneNumberLucasLehmerGpuTester return false; } - if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne()) + if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu)) { return false; } diff --git a/PerfectNumbers.Core/Gpu/Pow2MontgomeryGpuCalculator.cs b/PerfectNumbers.Core/Gpu/Pow2MontgomeryGpuCalculator.cs index 95ecff5..95fe1d4 100644 --- a/PerfectNumbers.Core/Gpu/Pow2MontgomeryGpuCalculator.cs +++ b/PerfectNumbers.Core/Gpu/Pow2MontgomeryGpuCalculator.cs @@ -8,12 +8,11 @@ namespace PerfectNumbers.Core.Gpu; public static class Pow2MontgomeryGpuCalculator { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong CalculateKeep(ulong exponent, in MontgomeryDivisorData divisor) + public static ulong CalculateKeep(PrimeOrderCalculatorAccelerator gpu, ulong exponent, in MontgomeryDivisorData divisor) { // GpuPrimeWorkLimiter.Acquire(); ulong result = 0UL; - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); int acceleratorIndex = gpu.AcceleratorIndex; Accelerator? accelerator = gpu.Accelerator; MemoryBuffer1D exponentBuffer = gpu.Input; @@ -32,37 +31,6 @@ public static class Pow2MontgomeryGpuCalculator AcceleratorStreamPool.Return(acceleratorIndex, stream); // GpuPrimeWorkLimiter.Release(); - PrimeOrderCalculatorAccelerator.Return(gpu); - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong CalculateConvert(ulong exponent, in MontgomeryDivisorData divisor) - { - // GpuPrimeWorkLimiter.Acquire(); - - ulong result = 0UL; - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); - MemoryBuffer1D exponentBuffer = gpu.Input; - MemoryBuffer1D resultBuffer = gpu.OutputUlong; - - int acceleratorIndex = gpu.AcceleratorIndex; - AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); - exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1); - // We don't need to worry about any left-overs here. - // resultBuffer.MemSetToZero(stream); - - var kernelLauncher = gpu.ConvertToStandardKernelLauncher!; - - kernelLauncher(stream, 1, exponentBuffer.View, divisor.Modulus, divisor.NPrime, divisor.MontgomeryOne, divisor.MontgomeryTwo, divisor.MontgomeryTwoSquared, resultBuffer.View); - - resultBuffer.View.CopyToCPU(stream, ref result, 1); - stream.Synchronize(); - - AcceleratorStreamPool.Return(acceleratorIndex, stream); - // GpuPrimeWorkLimiter.Release(); - PrimeOrderCalculatorAccelerator.Return(gpu); - return result; } diff --git a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs index 7f2d36f..f450593 100644 --- a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs +++ b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs @@ -243,7 +243,7 @@ internal static partial class PrimeOrderGpuHeuristics } } - public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder, in MontgomeryDivisorData divisorData) + public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder, in MontgomeryDivisorData? divisorData) { Span exponents = stackalloc ulong[1]; Span remainders = stackalloc ulong[1]; @@ -254,7 +254,7 @@ internal static partial class PrimeOrderGpuHeuristics return status; } - public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan exponents, ulong prime, Span remainders, in MontgomeryDivisorData divisorData) + public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan exponents, ulong prime, Span remainders, in MontgomeryDivisorData? divisorData) { // This will never occur in production code // if (exponents.Length == 0) @@ -309,20 +309,20 @@ internal static partial class PrimeOrderGpuHeuristics return computed ? GpuPow2ModStatus.Success : GpuPow2ModStatus.Unavailable; } - public static GpuPow2ModStatus TryPow2Mod(in UInt128 exponent, in UInt128 prime, out UInt128 remainder) + public static GpuPow2ModStatus TryPow2Mod(PrimeOrderCalculatorAccelerator gpu, in UInt128 exponent, in UInt128 prime, out UInt128 remainder) { Span exponents = stackalloc UInt128[1]; Span remainders = stackalloc UInt128[1]; exponents[0] = exponent; - GpuPow2ModStatus status = TryPow2ModBatch(exponents, prime, remainders); + GpuPow2ModStatus status = TryPow2ModBatch(gpu, exponents, prime, remainders); remainder = remainders[0]; return status; } - public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan exponents, UInt128 prime, Span remainders) + public static GpuPow2ModStatus TryPow2ModBatch(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan exponents, UInt128 prime, Span remainders) { - return TryPow2ModBatchInternal(exponents, prime, remainders); + return TryPow2ModBatchInternal(gpu, exponents, prime, remainders); } internal static bool TryCalculateOrder( @@ -471,7 +471,7 @@ internal static partial class PrimeOrderGpuHeuristics return kernel; } - private static GpuPow2ModStatus TryPow2ModBatchInternal(ReadOnlySpan exponents, UInt128 prime, Span remainders) + private static GpuPow2ModStatus TryPow2ModBatchInternal(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan exponents, UInt128 prime, Span remainders) { if (exponents.Length == 0) { @@ -509,11 +509,11 @@ internal static partial class PrimeOrderGpuHeuristics } } - ComputeOnGpuWide(exponents, prime, target); + ComputeOnGpuWide(gpu, exponents, prime, target); return GpuPow2ModStatus.Success; } - private static bool TryComputeOnGpu(ReadOnlySpan exponents, ulong prime, in MontgomeryDivisorData divisorData, Span results) + private static bool TryComputeOnGpu(ReadOnlySpan exponents, ulong prime, MontgomeryDivisorData? divisorData, Span results) { // GpuPrimeWorkLimiter.Acquire(); int acceleratorIndex = AcceleratorPool.Shared.Rent(); @@ -540,6 +540,11 @@ internal static partial class PrimeOrderGpuHeuristics try { + if (divisorData is null) + { + divisorData = MontgomeryDivisorData.Empty; + } + kernelLauncher(stream, exponents.Length, exponentBuffer.View, divisorData.Modulus, divisorData.NPrime, divisorData.MontgomeryOne, divisorData.MontgomeryTwo, divisorData.MontgomeryTwoSquared, remainderBuffer.View); remainderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(results), exponents.Length); @@ -573,14 +578,13 @@ internal static partial class PrimeOrderGpuHeuristics } } - private static void ComputeOnGpuWide(ReadOnlySpan exponents, UInt128 prime, Span results) + private static void ComputeOnGpuWide(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan exponents, UInt128 prime, Span results) { int length = exponents.Length; GpuUInt128[]? rentedExponents = null; GpuUInt128[]? rentedResults = null; ArrayPool gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool; - PrimeOrderCalculatorAccelerator? gpu = null; bool poolingRequired = length > WideStackThreshold; Span exponentSpan = poolingRequired ? stackalloc GpuUInt128[length] @@ -596,7 +600,6 @@ internal static partial class PrimeOrderGpuHeuristics } GpuUInt128 modulus = (GpuUInt128)prime; - gpu = PrimeOrderCalculatorAccelerator.Rent(1); int acceleratorIndex = gpu.AcceleratorIndex; Accelerator accelerator = gpu.Accelerator; @@ -625,7 +628,6 @@ internal static partial class PrimeOrderGpuHeuristics remainderBuffer.Dispose(); exponentBuffer.Dispose(); - PrimeOrderCalculatorAccelerator.Return(gpu); if (rentedExponents is not null) { diff --git a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs index ef679be..18a941a 100644 --- a/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicCombinedPrimeTester.cs @@ -168,15 +168,15 @@ public sealed class HeuristicCombinedPrimeTester return HeuristicTrialDivisionCpu(n, maxDivisorSquare, nMod10); } - public static bool IsPrimeGpu(ulong n) - { - byte nMod10 = (byte)n.Mod10(); - ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n); - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); - bool isPrime = IsPrimeGpu(gpu, n, maxDivisorSquare, nMod10); - PrimeOrderCalculatorAccelerator.Return(gpu); - return isPrime; - } + // public static bool IsPrimeGpu(ulong n) + // { + // byte nMod10 = (byte)n.Mod10(); + // ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n); + // var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + // bool isPrime = IsPrimeGpu(gpu, n, maxDivisorSquare, nMod10); + // PrimeOrderCalculatorAccelerator.Return(gpu); + // return isPrime; + // } public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n) { @@ -520,7 +520,7 @@ public sealed class HeuristicCombinedPrimeTester } primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL; - ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); + ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); cycleFromHint = false; cycleComputed = resolvedCycle != 0UL; return resolvedCycle; diff --git a/PerfectNumbers.Core/HeuristicPrimeTester.cs b/PerfectNumbers.Core/HeuristicPrimeTester.cs index f9f8c09..6a99a7e 100644 --- a/PerfectNumbers.Core/HeuristicPrimeTester.cs +++ b/PerfectNumbers.Core/HeuristicPrimeTester.cs @@ -83,17 +83,17 @@ public sealed class HeuristicPrimeTester } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPrimeGpu(ulong n) + public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n) { byte nMod10 = (byte)n.Mod10(); ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n); - return IsPrimeGpu(n, maxDivisorSquare, nMod10); + return IsPrimeGpu(gpu, n, maxDivisorSquare, nMod10); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPrimeGpu(ulong n, ulong maxDivisorSquare, byte nMod10) => HeuristicIsPrimeGpuCore(n, maxDivisorSquare, nMod10); + public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n, ulong maxDivisorSquare, byte nMod10) => HeuristicIsPrimeGpuCore(gpu, n, maxDivisorSquare, nMod10); - private static bool HeuristicIsPrimeGpuCore(ulong n, ulong maxDivisorSquare, byte nMod10) + private static bool HeuristicIsPrimeGpuCore(PrimeOrderCalculatorAccelerator gpu, ulong n, ulong maxDivisorSquare, byte nMod10) { // TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner? if (maxDivisorSquare < 9UL) @@ -106,7 +106,7 @@ public sealed class HeuristicPrimeTester // return HeuristicTrialDivisionCpu(n, maxDivisorSquare, nMod10, includeGroupB: false); // } - bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, maxDivisorSquare, nMod10); + bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(gpu, n, maxDivisorSquare, nMod10); return !compositeDetected; } @@ -239,10 +239,9 @@ public sealed class HeuristicPrimeTester } - private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10) + private static bool HeuristicTrialDivisionGpuDetectsDivisor(PrimeOrderCalculatorAccelerator gpu, ulong n, ulong maxDivisorSquare, byte nMod10) { // GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); int acceleratorIndex = gpu.AcceleratorIndex; var stream = AcceleratorStreamPool.Rent(acceleratorIndex); var flagView1D = gpu.OutputInt!.View; @@ -313,7 +312,6 @@ public sealed class HeuristicPrimeTester // } AcceleratorStreamPool.Return(acceleratorIndex, stream); - PrimeOrderCalculatorAccelerator.Return(gpu); // GpuPrimeWorkLimiter.Release(); return compositeDetected; } @@ -487,7 +485,7 @@ public sealed class HeuristicPrimeTester } primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL; - ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); + ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); cycleFromHint = false; cycleComputed = resolvedCycle != 0UL; return resolvedCycle; diff --git a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs index e93d1b5..b060f43 100644 --- a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs @@ -1,3 +1,5 @@ +using PerfectNumbers.Core.Gpu.Accelerators; + namespace PerfectNumbers.Core; public interface IMersenneNumberDivisorByDivisorTester @@ -10,9 +12,9 @@ public interface IMersenneNumberDivisorByDivisorTester ulong GetAllowedMaxDivisor(ulong prime); - bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor); + bool IsPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime, out bool divisorsExhausted, out ulong divisor); - IDivisorScanSession CreateDivisorSession(); + IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu); void PrepareCandidates(in ReadOnlySpan primes, Span allowedMaxValues); diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 576ae10..61a28c5 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -75,7 +75,7 @@ public class MersenneDivisorCycles } } - if (TryCalculateCycleLengthHeuristic(divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL) + if (TryCalculateCycleLengthHeuristic(gpu, divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL) { return heuristicCycle == exponent; } @@ -163,7 +163,7 @@ public class MersenneDivisorCycles } } - public ulong GetCycle(ulong divisor, in MontgomeryDivisorData divisorData) + public ulong GetCycle(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData) { // Fast-path: in-memory array for small divisors if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles) @@ -208,10 +208,10 @@ public class MersenneDivisorCycles // TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current // configuration (and skip persisting the result) when the divisor falls outside the in-memory snapshot // so we respect the large-p memory limits and avoid extra cache locks. - return CalculateCycleLength(divisor, divisorData); + return CalculateCycleLength(gpu, divisor, divisorData); } - public static UInt128 GetCycle(UInt128 divisor) + public static UInt128 GetCycle(PrimeOrderCalculatorAccelerator gpu, UInt128 divisor) { // For divisor = 2^k, cycle is 1 if ((divisor & (divisor - UInt128.One)) == UInt128.Zero) @@ -224,12 +224,13 @@ public class MersenneDivisorCycles ulong divisor64 = (ulong)divisor; Queue divisorPool = MontgomeryDivisorDataPool.Shared; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor64); - ulong cycle = CalculateCycleLength(divisor64, divisorData); + ulong cycle = CalculateCycleLength(gpu, divisor64, divisorData); divisorPool.Return(divisorData); return (UInt128)cycle; } UInt128 wideOrder = PrimeOrderCalculator.Calculate( + gpu, divisor, previousOrder: null, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, @@ -285,6 +286,7 @@ public class MersenneDivisorCycles // } ulong computedOrder = PrimeOrderCalculator.Calculate( + gpu, divisor, previousOrder: null, divisorData, @@ -743,6 +745,7 @@ public class MersenneDivisorCycles // (ulong divisor, ulong cycle)[] localCycles = ArrayPool<(ulong, ulong)>.Shared.Rent(checked((int)rangeLength)); int localCycleIndex = 0; + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (ulong divisor = localStart; divisor <= localEnd; divisor++) { if ((divisor & 1UL) == 0UL) @@ -762,11 +765,12 @@ public class MersenneDivisorCycles // unrolled-hex calculator so the CPU generator matches // the GPU benchmark leader for large divisors. MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor); - ulong cycle = CalculateCycleLength(divisor, divisorData); + ulong cycle = CalculateCycleLength(gpu, divisor, divisorData); divisorPool.Return(divisorData); localCycles[localCycleIndex++] = (divisor, cycle); } + PrimeOrderCalculatorAccelerator.Return(gpu); if (localCycleIndex == 0) { ThreadStaticPools.Return(localCycles); @@ -929,9 +933,9 @@ public class MersenneDivisorCycles public static ulong CalculateCycleLengthGpu(ulong divisor) => DivisorCycleKernels.CalculateCycleLengthGpu(divisor); - public static ulong CalculateCycleLength(ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false) + public static ulong CalculateCycleLength(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false) { - if (TryCalculateCycleLengthHeuristic(divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic)) + if (TryCalculateCycleLengthHeuristic(gpu, divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic)) return cycleLength; return CalculateCycleLengthFallback(divisor); @@ -940,7 +944,7 @@ public class MersenneDivisorCycles // [ThreadStatic] // private static HeuristicPrimeTester? _primeTester; - internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false) + internal static bool TryCalculateCycleLengthHeuristic(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false) { if ((divisor & (divisor - 1UL)) == 0UL) { @@ -966,6 +970,7 @@ public class MersenneDivisorCycles // if (!skipPrimeOrderHeuristic && HeuristicCombinedPrimeTester.IsPrimeGpu(divisor)) { ulong computedOrder = PrimeOrderCalculator.Calculate( + gpu, divisor, previousOrder: null, divisorData, diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index 728365d..a48fd49 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -239,10 +239,10 @@ public static class MersenneNumberDivisorByDivisorTester int chunkSize = primesPerTask <= 0 ? 1 : primesPerTask; - void ProcessPrime(ulong prime) + void ProcessPrime(PrimeOrderCalculatorAccelerator gpu, ulong prime) { Console.WriteLine($"Processing {prime}"); - bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted, out ulong divisor); + bool isPrime = tester.IsPrime(gpu, prime, out bool divisorsExhausted, out ulong divisor); if (!isPrime) { @@ -259,10 +259,13 @@ public static class MersenneNumberDivisorByDivisorTester if (workerCount == 1) { + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); foreach (ulong prime in filteredPrimes) { - ProcessPrime(prime); + ProcessPrime(gpu, prime); } + + PrimeOrderCalculatorAccelerator.Return(gpu); } else { @@ -295,11 +298,13 @@ public static class MersenneNumberDivisorByDivisorTester startGate.Wait(); Console.WriteLine($"Task started for range {rangeStart}"); + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (int index = rangeStart; index < rangeEnd; index++) { - ProcessPrime(filteredPrimes[index]); + ProcessPrime(gpu, filteredPrimes[index]); } + PrimeOrderCalculatorAccelerator.Return(gpu); Console.WriteLine($"Task finished for range {rangeStart}"); PrimeOrderCalculatorAccelerator.DisposeAll(); }, diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index 2719d3e..74c5313 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -63,6 +63,7 @@ public sealed class MersenneNumberTester( UInt128 twoP = (UInt128)exponent << 1; // 2 * p LastDigit lastDigit = (exponent & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; // CPU path: compute orders directly and cache + var gpu = PrimeOrderCalculatorAccelerator.Rent(1); if (!_useGpuOrder) { UInt128 k = UInt128.One; @@ -97,10 +98,11 @@ public sealed class MersenneNumberTester( continue; } - ulong ord = q.CalculateOrder(); + ulong ord = q.CalculateOrder(gpu); _orderCache[q] = ord; } + PrimeOrderCalculatorAccelerator.Return(gpu); return; } @@ -190,7 +192,7 @@ public sealed class MersenneNumberTester( ulong order = orders[i]; if (order == 0UL) { - order = qs[offset + i].CalculateOrder(); + order = qs[offset + i].CalculateOrder(gpu); } if (useOrderCache) @@ -205,11 +207,12 @@ public sealed class MersenneNumberTester( } AcceleratorStreamPool.Return(acceleratorIndex,stream); + PrimeOrderCalculatorAccelerator.Return(gpu); // GpuPrimeWorkLimiter.Release(); uInt128Pool.Return(qs); } - public bool IsMersennePrime(ulong exponent) + public bool IsMersennePrime(PrimeOrderCalculatorAccelerator gpu, ulong exponent) { // Safe early rejections using known orders for tiny primes: // 7 | M_p iff 3 | p. Avoid rejecting p == 3 where M_3 == 7 is prime. @@ -220,7 +223,7 @@ public sealed class MersenneNumberTester( return false; } - if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne()) + if ((exponent & 3UL) == 1UL && exponent.SharesFactorWithExponentMinusOne(gpu)) { return false; } @@ -251,7 +254,7 @@ public sealed class MersenneNumberTester( } else { - _residueCpuTester!.Scan(exponent, twoP, lastDigit, maxK, ref prePrime); + _residueCpuTester!.Scan(gpu, exponent, twoP, lastDigit, maxK, ref prePrime); } return prePrime; @@ -270,7 +273,7 @@ public sealed class MersenneNumberTester( } else { - _incrementalCpuTester.Scan(exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); + _incrementalCpuTester.Scan(gpu, exponent, twoPPre, lastDigitPre, maxKPre, ref prePrime); } if (!prePrime) @@ -279,8 +282,8 @@ public sealed class MersenneNumberTester( } return _useGpuLucas - ? _lucasLehmerGpuTester.IsPrime(exponent, runOnGpu: true) - : _lucasLehmerCpuTester.IsPrime(exponent); + ? _lucasLehmerGpuTester.IsPrime(gpu, exponent, runOnGpu: true) + : _lucasLehmerCpuTester.IsPrime(gpu, exponent); } bool isPrime = true; @@ -293,7 +296,7 @@ public sealed class MersenneNumberTester( } else { - _orderCpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); + _orderCpuTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref isPrime); } } else @@ -304,7 +307,7 @@ public sealed class MersenneNumberTester( } else { - _incrementalCpuTester.Scan(exponent, twoP, lastDigit, maxK, ref isPrime); + _incrementalCpuTester.Scan(gpu, exponent, twoP, lastDigit, maxK, ref isPrime); } } diff --git a/PerfectNumbers.Core/ModResidueTracker.cs b/PerfectNumbers.Core/ModResidueTracker.cs index 89ffb72..33bd5ba 100644 --- a/PerfectNumbers.Core/ModResidueTracker.cs +++ b/PerfectNumbers.Core/ModResidueTracker.cs @@ -1,5 +1,5 @@ using System.Runtime.CompilerServices; - +using PerfectNumbers.Core.Gpu.Accelerators; using UInt128 = System.UInt128; namespace PerfectNumbers.Core; @@ -39,7 +39,7 @@ public sealed class ModResidueTracker /// (number must be non-decreasing across calls). Adds and initializes new /// divisors on first use. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDivisible(UInt128 number, UInt128 divisor) + public bool IsDivisible(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor) { if (divisor == UInt128.Zero) { @@ -52,7 +52,7 @@ public sealed class ModResidueTracker int idx = LowerBound(divisors, divisor); if (idx == divisors.Count || divisors[idx] != divisor) { - UInt128 residue = ComputeInitialResidue(number, divisor); + UInt128 residue = ComputeInitialResidue(gpu, number, divisor); divisors.Insert(idx, divisor); _residues.Insert(idx, residue); if (_hasLastAppended && idx == divisors.Count - 1 && divisor >= _lastAppended) @@ -81,7 +81,7 @@ public sealed class ModResidueTracker // Merge or append current candidate divisor (ascending order required across calls). // Returns true and sets 'divisible' based on residue at 'number'. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MergeOrAppend(UInt128 number, UInt128 divisor, out bool divisible) + public bool MergeOrAppend(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor, out bool divisible) { // Assume number is non-decreasing; advance residues lazily when number changes. List divisors = _divisors; @@ -97,7 +97,7 @@ public sealed class ModResidueTracker List residues = _residues; if (divisors.Count == 0 || (_hasLastAppended && divisor >= _lastAppended && divisors[^1] <= divisor)) { - residue = ComputeInitialResidue(number, divisor); + residue = ComputeInitialResidue(gpu, number, divisor); divisors.Add(divisor); residues.Add(residue); _hasLastAppended = true; @@ -123,7 +123,7 @@ public sealed class ModResidueTracker // Insert at cursor to keep order - residue = ComputeInitialResidue(number, divisor); + residue = ComputeInitialResidue(gpu, number, divisor); divisors.Insert(cursor, divisor); residues.Insert(cursor, residue); divisible = residue == UInt128.Zero; @@ -238,7 +238,7 @@ public sealed class ModResidueTracker } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private UInt128 ComputeInitialResidue(UInt128 number, UInt128 divisor) + private UInt128 ComputeInitialResidue(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor) { if (_model == ResidueModel.Identity) { @@ -266,7 +266,7 @@ public sealed class ModResidueTracker if (pow != UInt128.One) return (pow - UInt128.One) % divisor; - ulong ord = divisor.CalculateOrder(); + ulong ord = divisor.CalculateOrder(gpu); if (ord == 0UL || number % (UInt128)ord != UInt128.Zero) return UInt128.One; // does NOT divide diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 7dc2454..53b4e08 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -44,7 +44,7 @@ internal static partial class PrimeOrderCalculator } } - private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config) + private static ulong CalculateInternal(PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config) { // TODO: Is this condition ever met on EvenPerfectBitScanner's execution path? If not, we can add a clarification comment and comment out the entire block. We want to support p candidates at least greater or equal to 31. if (prime <= 3UL) @@ -54,10 +54,8 @@ internal static partial class PrimeOrderCalculator ulong phi = prime - 1UL; - PrimeOrderCalculatorAccelerator gpu = PrimeOrderCalculatorAccelerator.Rent(1); if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(gpu, prime, previousOrder, config, divisorData, out ulong gpuOrder)) { - PrimeOrderCalculatorAccelerator.Return(gpu); return gpuOrder; } @@ -73,13 +71,11 @@ internal static partial class PrimeOrderCalculator // result = CalculateByFactorizationCpu(gpu, prime, divisorData); phiFactors.Dispose(); - PrimeOrderCalculatorAccelerator.Return(gpu); return result; } result = RunHeuristicPipelineCpu(gpu, prime, previousOrder, config, divisorData, phi, phiFactors); phiFactors.Dispose(); - PrimeOrderCalculatorAccelerator.Return(gpu); return result; } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs index 8734c4d..951fc18 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs @@ -2,6 +2,7 @@ using System.Buffers; using System.Diagnostics; using System.Numerics; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core; @@ -82,7 +83,7 @@ internal static partial class PrimeOrderCalculator Console.WriteLine(messageFactory()); } } - private static UInt128 CalculateWideInternal(in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config) + private static UInt128 CalculateWideInternal(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config) { if (prime <= UInt128.One) { @@ -100,11 +101,11 @@ internal static partial class PrimeOrderCalculator UInt128 result; if (phiFactors.Factors is null) { - result = FinishStrictlyWide(prime, divisorData); + result = FinishStrictlyWide(gpu, prime, divisorData); } else { - result = RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors); + result = RunHeuristicPipelineWide(gpu, prime, previousOrder, divisorData, config, phi, phiFactors); } debugScope.Dispose(); @@ -112,32 +113,33 @@ internal static partial class PrimeOrderCalculator } private static UInt128 RunHeuristicPipelineWide( + PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, - in MontgomeryDivisorData divisorData, + in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config, in UInt128 phi, in PartialFactorResult128 phiFactors) { - if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors)) + if (phiFactors.FullyFactored && TrySpecialMaxWide(gpu, phi, prime, divisorData, phiFactors)) { return phi; } - UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData); - candidateOrder = ExponentLoweringWide(candidateOrder, prime, divisorData, phiFactors); + UInt128 candidateOrder = InitializeStartingOrderWide(gpu, prime, phi, divisorData); + candidateOrder = ExponentLoweringWide(gpu, candidateOrder, prime, divisorData, phiFactors); - if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config)) + if (TryConfirmOrderWide(gpu, prime, candidateOrder, divisorData, config)) { return candidateOrder; } if (config.Mode == PrimeOrderMode.Strict) { - return FinishStrictlyWide(prime, divisorData); + return FinishStrictlyWide(gpu, prime, divisorData); } - if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order)) + if (TryHeuristicFinishWide(gpu, prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order)) { return order; } @@ -145,12 +147,12 @@ internal static partial class PrimeOrderCalculator return candidateOrder; } - private static UInt128 FinishStrictlyWide(in UInt128 prime, in MontgomeryDivisorData divisorData) + private static UInt128 FinishStrictlyWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in MontgomeryDivisorData? divisorData) { - return CalculateByFactorizationWide(prime, divisorData); + return CalculateByFactorizationWide(gpu, prime, divisorData); } - private static bool TrySpecialMaxWide(in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors) + private static bool TrySpecialMaxWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData? divisorData, in PartialFactorResult128 factors) { ReadOnlySpan factorSpan = factors.Factors; int length = factors.Count; @@ -158,7 +160,7 @@ internal static partial class PrimeOrderCalculator { UInt128 factor = factorSpan[i].Value; UInt128 reduced = phi / factor; - if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One) + if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One) { return false; } @@ -167,14 +169,14 @@ internal static partial class PrimeOrderCalculator return true; } - private static UInt128 InitializeStartingOrderWide(in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData divisorData) + private static UInt128 InitializeStartingOrderWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData? divisorData) { UInt128 order = phi; UInt128 mod8 = prime & (UInt128)7UL; if (mod8 == UInt128.One || mod8 == (UInt128)7UL) { UInt128 half = phi >> 1; - if (Pow2ModWide(half, prime, divisorData) == UInt128.One) + if (Pow2ModWide(gpu, half, prime, divisorData) == UInt128.One) { order = half; } @@ -183,7 +185,7 @@ internal static partial class PrimeOrderCalculator return order; } - private static UInt128 ExponentLoweringWide(UInt128 order, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors) + private static UInt128 ExponentLoweringWide(PrimeOrderCalculatorAccelerator gpu, UInt128 order, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors) { ArrayPool pool = ThreadStaticPools.FactorEntry128Pool; ReadOnlySpan factorSpan = factors.Factors; @@ -215,7 +217,7 @@ internal static partial class PrimeOrderCalculator if ((order % primeFactor) == UInt128.Zero) { UInt128 reduced = order / primeFactor; - if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One) + if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One) { order = reduced; continue; @@ -230,14 +232,14 @@ internal static partial class PrimeOrderCalculator return order; } - private static bool TryConfirmOrderWide(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config) + private static bool TryConfirmOrderWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config) { if (order == UInt128.Zero) { return false; } - if (Pow2ModWide(order, prime, divisorData) != UInt128.One) + if (Pow2ModWide(gpu, order, prime, divisorData) != UInt128.One) { return false; } @@ -277,7 +279,7 @@ internal static partial class PrimeOrderCalculator } reduced /= primeFactor; - if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One) + if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One) { return false; } @@ -288,6 +290,7 @@ internal static partial class PrimeOrderCalculator } private static bool TryHeuristicFinishWide( + PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 order, in UInt128? previousOrder, @@ -347,12 +350,12 @@ internal static partial class PrimeOrderCalculator UInt128 candidate = candidates[i]; powUsed++; - if (Pow2ModWide(candidate, prime, divisorData) != UInt128.One) + if (Pow2ModWide(gpu, candidate, prime, divisorData) != UInt128.One) { continue; } - if (!TryConfirmCandidateWide(prime, candidate, config, ref powUsed, powBudget, divisorData)) + if (!TryConfirmCandidateWide(gpu, prime, candidate, config, ref powUsed, powBudget, divisorData)) { continue; } @@ -536,7 +539,7 @@ internal static partial class PrimeOrderCalculator } } - private static bool TryConfirmCandidateWide(in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData) + private static bool TryConfirmCandidateWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData) { PartialFactorResult128 factorization = PartialFactorWide(candidate, config); if (factorization.Factors is null) @@ -579,7 +582,7 @@ internal static partial class PrimeOrderCalculator } powUsed++; - if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One) + if (Pow2ModWide(gpu, reduced, prime, divisorData) == UInt128.One) { return false; } @@ -855,7 +858,7 @@ internal static partial class PrimeOrderCalculator return 64 + BitOperations.TrailingZeroCount(high); } - private static UInt128 CalculateByFactorizationWide(in UInt128 prime, in MontgomeryDivisorData divisorData) + private static UInt128 CalculateByFactorizationWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in MontgomeryDivisorData? divisorData) { UInt128 phi = prime - UInt128.One; Dictionary counts = new(capacity: 8); @@ -879,7 +882,7 @@ internal static partial class PrimeOrderCalculator if ((order % primeFactor) != UInt128.Zero) { UInt128 candidate = order / primeFactor; - if (Pow2ModWide(candidate, prime, divisorData) == UInt128.One) + if (Pow2ModWide(gpu, candidate, prime, divisorData) == UInt128.One) { order = candidate; } @@ -1026,7 +1029,7 @@ internal static partial class PrimeOrderCalculator return true; } - private static UInt128 Pow2ModWide(in UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData) + private static UInt128 Pow2ModWide(PrimeOrderCalculatorAccelerator gpu, in UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData? divisorData) { if (modulus == UInt128.One) { @@ -1046,7 +1049,7 @@ internal static partial class PrimeOrderCalculator } } - GpuPow2ModStatus wideStatus = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, modulus, out UInt128 wideRemainder); + GpuPow2ModStatus wideStatus = PrimeOrderGpuHeuristics.TryPow2Mod(gpu, exponent, modulus, out UInt128 wideRemainder); if (wideStatus == GpuPow2ModStatus.Success) { return wideRemainder; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index 70180d1..6198919 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core; @@ -48,6 +49,7 @@ internal static partial class PrimeOrderCalculator // TODO: Remove branching on the CPU / GPU paths // TODO: Split big non-static functions into smaller / extract static code to limit JIT time public static ulong Calculate( + PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, @@ -55,12 +57,13 @@ internal static partial class PrimeOrderCalculator PrimeOrderHeuristicDevice device) { var scope = UsePow2Mode(device); - ulong order = CalculateInternal(prime, previousOrder, divisorData, config); + ulong order = CalculateInternal(gpu, prime, previousOrder, divisorData, config); scope.Dispose(); return order; } public static UInt128 Calculate( + PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, in PrimeOrderSearchConfig config, @@ -88,14 +91,14 @@ internal static partial class PrimeOrderCalculator ulong prime64 = (ulong)prime; Queue divisorPool = MontgomeryDivisorDataPool.Shared; divisorData = divisorPool.FromModulus(prime64); - ulong order64 = Calculate(prime64, previous, divisorData, config, device); + ulong order64 = Calculate(gpu, prime64, previous, divisorData, config, device); divisorPool.Return(divisorData); result = order64 == 0UL ? UInt128.Zero : (UInt128)order64; } else { divisorData = MontgomeryDivisorData.Empty; - result = CalculateWideInternal(prime, previousOrder, divisorData, config); + result = CalculateWideInternal(gpu, prime, previousOrder, divisorData, config); } scope.Dispose(); diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index 4aef0da..e0abd2d 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -87,45 +87,45 @@ public sealed class PrimeTester return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPrimeGpu(ulong n) - { + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // public static bool IsPrimeGpu(ulong n) + // { - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); - int acceleratorIndex = gpu.AcceleratorIndex; - var inputView = gpu.Input.View; - var outputView = gpu.OutputByte.View; + // var gpu = PrimeOrderCalculatorAccelerator.Rent(1); + // int acceleratorIndex = gpu.AcceleratorIndex; + // var inputView = gpu.Input.View; + // var outputView = gpu.OutputByte.View; - // GpuPrimeWorkLimiter.Acquire(); - AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); - inputView.CopyFromCPU(stream, ref n, 1); + // // GpuPrimeWorkLimiter.Acquire(); + // AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); + // inputView.CopyFromCPU(stream, ref n, 1); - var kernelLauncher = gpu.SmallPrimeSieveKernelLauncher; + // var kernelLauncher = gpu.SmallPrimeSieveKernelLauncher; - kernelLauncher( - stream, - 1, - inputView, - gpu.DevicePrimesLastOne, - gpu.DevicePrimesLastSeven, - gpu.DevicePrimesLastThree, - gpu.DevicePrimesLastNine, - gpu.DevicePrimesPow2LastOne, - gpu.DevicePrimesPow2LastSeven, - gpu.DevicePrimesPow2LastThree, - gpu.DevicePrimesPow2LastNine, - outputView); + // kernelLauncher( + // stream, + // 1, + // inputView, + // gpu.DevicePrimesLastOne, + // gpu.DevicePrimesLastSeven, + // gpu.DevicePrimesLastThree, + // gpu.DevicePrimesLastNine, + // gpu.DevicePrimesPow2LastOne, + // gpu.DevicePrimesPow2LastSeven, + // gpu.DevicePrimesPow2LastThree, + // gpu.DevicePrimesPow2LastNine, + // outputView); - byte flag = 0; - outputView.CopyToCPU(stream, ref flag, 1); - stream.Synchronize(); + // byte flag = 0; + // outputView.CopyToCPU(stream, ref flag, 1); + // stream.Synchronize(); - AcceleratorStreamPool.Return(acceleratorIndex, stream); - PrimeOrderCalculatorAccelerator.Return(gpu); - // GpuPrimeWorkLimiter.Release(); + // AcceleratorStreamPool.Return(acceleratorIndex, stream); + // PrimeOrderCalculatorAccelerator.Return(gpu); + // // GpuPrimeWorkLimiter.Release(); - return flag != 0; - } + // return flag != 0; + // } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPrimeGpu(PrimeOrderCalculatorAccelerator gpu, ulong n) @@ -189,10 +189,9 @@ public sealed class PrimeTester } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsPrimeBatchGpu(ReadOnlySpan values, Span results) + public static void IsPrimeBatchGpu(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan values, Span results) { // GpuPrimeWorkLimiter.Acquire(); - var gpu = PrimeOrderCalculatorAccelerator.Rent(GpuBatchSize); int acceleratorIndex = gpu.AcceleratorIndex; int totalLength = values.Length; int batchSize = GpuBatchSize; @@ -236,7 +235,6 @@ public sealed class PrimeTester stream.Synchronize(); AcceleratorStreamPool.Return(acceleratorIndex, stream); - PrimeOrderCalculatorAccelerator.Return(gpu); // GpuPrimeWorkLimiter.Release(); } @@ -265,7 +263,7 @@ public sealed class PrimeTester return n.BinaryGcd(m) != 1UL; } - internal static void SharesFactorWithMaxExponentBatch(ReadOnlySpan values, Span results) + internal static void SharesFactorWithMaxExponentBatch(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan values, Span results) { // TODO: Route this batch helper through the shared GPU kernel pool from // GpuUInt128BinaryGcdBenchmarks so we reuse cached kernels, pinned host buffers, @@ -275,7 +273,6 @@ public sealed class PrimeTester int length = values.Length; - var gpu = PrimeOrderCalculatorAccelerator.Rent(1); int acceleratorIndex = gpu.AcceleratorIndex; gpu.EnsureCapacity(0, length); MemoryBuffer1D? inputBuffer = gpu.Input; @@ -291,7 +288,6 @@ public sealed class PrimeTester stream.Synchronize(); AcceleratorStreamPool.Return(acceleratorIndex, stream); - PrimeOrderCalculatorAccelerator.Return(gpu); } } diff --git a/PerfectNumbers.Core/UInt128Extensions.cs b/PerfectNumbers.Core/UInt128Extensions.cs index 3e420e2..63b609f 100644 --- a/PerfectNumbers.Core/UInt128Extensions.cs +++ b/PerfectNumbers.Core/UInt128Extensions.cs @@ -1,866 +1,867 @@ using System.Numerics; using System.Runtime.CompilerServices; using PerfectNumbers.Core.Gpu; +using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core; public static class UInt128Extensions { - private const int Pow2WindowSize = 8; - private const ulong Pow2WindowFallbackThreshold = 32UL; + private const int Pow2WindowSize = 8; + private const ulong Pow2WindowFallbackThreshold = 32UL; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 BinaryGcd(this UInt128 u, UInt128 v) - { - // TODO: Replace this hand-rolled binary GCD with the shared subtract-free ladder from - // GpuUInt128BinaryGcdBenchmarks so wide operands reuse the optimized helper instead of - // repeating the slower shift/subtract loop on both CPU and GPU paths. - UInt128 zero = UInt128.Zero; - if (u == zero) - { - return v; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 BinaryGcd(this UInt128 u, UInt128 v) + { + // TODO: Replace this hand-rolled binary GCD with the shared subtract-free ladder from + // GpuUInt128BinaryGcdBenchmarks so wide operands reuse the optimized helper instead of + // repeating the slower shift/subtract loop on both CPU and GPU paths. + UInt128 zero = UInt128.Zero; + if (u == zero) + { + return v; + } - if (v == zero) - { - return u; - } + if (v == zero) + { + return u; + } - int shift = CountTrailingZeros(u | v); - u >>= CountTrailingZeros(u); + int shift = CountTrailingZeros(u | v); + u >>= CountTrailingZeros(u); - do - { - v >>= CountTrailingZeros(v); - if (u > v) - { - (u, v) = (v, u); - } + do + { + v >>= CountTrailingZeros(v); + if (u > v) + { + (u, v) = (v, u); + } - v -= u; - } - while (v != zero); + v -= u; + } + while (v != zero); - return u << shift; - } + return u << shift; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBitLength(this UInt128 value) - { - ulong high = (ulong)(value >> 64); - if (high != 0UL) - { - return 64 + (64 - BitOperations.LeadingZeroCount(high)); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBitLength(this UInt128 value) + { + ulong high = (ulong)(value >> 64); + if (high != 0UL) + { + return 64 + (64 - BitOperations.LeadingZeroCount(high)); + } - ulong low = (ulong)value; - if (low == 0UL) - { - return 0; - } + ulong low = (ulong)value; + if (low == 0UL) + { + return 0; + } - return 64 - BitOperations.LeadingZeroCount(low); - } + return 64 - BitOperations.LeadingZeroCount(low); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 Pow2MontgomeryModWindowed(this ulong exponent, UInt128 modulus) - { - // EvenPerfectBitScanner keeps CPU exponents above Pow2WindowFallbackThreshold once p >= 138,000,000, - // so production never routes the tiny-exponent single-bit fallback through this helper. Leave the - // guard commented out for targeted microbenchmarks. - // This helper executes only on CPU fallbacks (PrimeOrderCalculator.Shared and PrimeOrderGpuHeuristics - // call it after the GPU heuristics decline), but we still use GpuUInt128 because its mutable Montgomery - // operations beat the pure-UInt128 prototype by roughly 12% in Pow2MontgomeryModBenchmarks. - // TODO: Swap in a CPU-native Montgomery ladder once it surpasses the shared struct so GPU and CPU tuning - // can diverge without compromising either path. - GpuUInt128 modulusGpu = new(modulus); - GpuUInt128 baseValue = new(2UL); - if (baseValue.CompareTo(modulusGpu) >= 0) - { - baseValue.Sub(modulusGpu); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 Pow2MontgomeryModWindowed(this ulong exponent, UInt128 modulus) + { + // EvenPerfectBitScanner keeps CPU exponents above Pow2WindowFallbackThreshold once p >= 138,000,000, + // so production never routes the tiny-exponent single-bit fallback through this helper. Leave the + // guard commented out for targeted microbenchmarks. + // This helper executes only on CPU fallbacks (PrimeOrderCalculator.Shared and PrimeOrderGpuHeuristics + // call it after the GPU heuristics decline), but we still use GpuUInt128 because its mutable Montgomery + // operations beat the pure-UInt128 prototype by roughly 12% in Pow2MontgomeryModBenchmarks. + // TODO: Swap in a CPU-native Montgomery ladder once it surpasses the shared struct so GPU and CPU tuning + // can diverge without compromising either path. + GpuUInt128 modulusGpu = new(modulus); + GpuUInt128 baseValue = new(2UL); + if (baseValue.CompareTo(modulusGpu) >= 0) + { + baseValue.Sub(modulusGpu); + } - // if (exponent <= Pow2WindowFallbackThreshold) - // { - // GpuUInt128 tinyResult = Pow2MontgomeryModSingleBit((UInt128)exponent, modulusGpu, baseValue); - // return (UInt128)tinyResult; - // } + // if (exponent <= Pow2WindowFallbackThreshold) + // { + // GpuUInt128 tinyResult = Pow2MontgomeryModSingleBit((UInt128)exponent, modulusGpu, baseValue); + // return (UInt128)tinyResult; + // } - GpuUInt128 exponentGpu = new(exponent); - int bitLength = exponentGpu.GetBitLength(); - int windowSize = GetWindowSize(bitLength); - int oddPowerCount = 1 << (windowSize - 1); + GpuUInt128 exponentGpu = new(exponent); + int bitLength = exponentGpu.GetBitLength(); + int windowSize = GetWindowSize(bitLength); + int oddPowerCount = 1 << (windowSize - 1); - Span oddPowers = stackalloc GpuUInt128[oddPowerCount]; - InitializeOddPowers(baseValue, modulusGpu, oddPowers); + Span oddPowers = stackalloc GpuUInt128[oddPowerCount]; + InitializeOddPowers(baseValue, modulusGpu, oddPowers); - GpuUInt128 result = GpuUInt128.One; - int index = bitLength - 1; + GpuUInt128 result = GpuUInt128.One; + int index = bitLength - 1; - while (index >= 0) - { - if (!IsBitSet(exponentGpu, index)) - { - result.MulMod(result, modulusGpu); - index--; - continue; - } + while (index >= 0) + { + if (!IsBitSet(exponentGpu, index)) + { + result.MulMod(result, modulusGpu); + index--; + continue; + } - int windowStart = index - windowSize + 1; - if (windowStart < 0) - { - windowStart = 0; - } + int windowStart = index - windowSize + 1; + if (windowStart < 0) + { + windowStart = 0; + } - while (!IsBitSet(exponentGpu, windowStart)) - { - windowStart++; - } + while (!IsBitSet(exponentGpu, windowStart)) + { + windowStart++; + } - int windowBitCount = index - windowStart + 1; - for (int square = 0; square < windowBitCount; square++) - { - result.MulMod(result, modulusGpu); - } + int windowBitCount = index - windowStart + 1; + for (int square = 0; square < windowBitCount; square++) + { + result.MulMod(result, modulusGpu); + } - ulong windowValue = ExtractWindowValue(exponentGpu, windowStart, windowBitCount); - int tableIndex = (int)((windowValue - 1UL) >> 1); - GpuUInt128 factor = oddPowers[tableIndex]; - result.MulMod(factor, modulusGpu); + ulong windowValue = ExtractWindowValue(exponentGpu, windowStart, windowBitCount); + int tableIndex = (int)((windowValue - 1UL) >> 1); + GpuUInt128 factor = oddPowers[tableIndex]; + result.MulMod(factor, modulusGpu); - index = windowStart - 1; - } + index = windowStart - 1; + } - return (UInt128)result; - } + return (UInt128)result; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 Pow2MontgomeryModWindowed(this UInt128 exponent, UInt128 modulus) - { - // TODO: Prototype a UInt128-native Pow2MontgomeryModWindowed path that matches the GPU implementation once a faster - // modular multiplication helper is available, so we can replace the shared GpuUInt128 usage on CPU fallbacks and let - // the GPU kernels continue using their dedicated ladder without cross-contaminating optimizations. - // All GPU kernels already route through Pow2MontgomeryModWindowedGpu, and Pow2MontgomeryModBenchmarks confirmed this - // windowed ladder stays the fastest CPU option with the current Montgomery helpers. - if (modulus == UInt128.One) - { - return UInt128.Zero; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 Pow2MontgomeryModWindowed(this UInt128 exponent, UInt128 modulus) + { + // TODO: Prototype a UInt128-native Pow2MontgomeryModWindowed path that matches the GPU implementation once a faster + // modular multiplication helper is available, so we can replace the shared GpuUInt128 usage on CPU fallbacks and let + // the GPU kernels continue using their dedicated ladder without cross-contaminating optimizations. + // All GPU kernels already route through Pow2MontgomeryModWindowedGpu, and Pow2MontgomeryModBenchmarks confirmed this + // windowed ladder stays the fastest CPU option with the current Montgomery helpers. + if (modulus == UInt128.One) + { + return UInt128.Zero; + } - if (exponent == UInt128.Zero) - { - return UInt128.One % modulus; - } + if (exponent == UInt128.Zero) + { + return UInt128.One % modulus; + } - // This helper still executes on CPU-only call sites, so we lean on the shared GpuUInt128 ladder until - // the TODO above lands and gives the CPU path its own Montgomery struct. - GpuUInt128 modulusGpu = new(modulus); - GpuUInt128 baseValue = new(2UL); - if (baseValue.CompareTo(modulusGpu) >= 0) - { - baseValue.Sub(modulusGpu); - } + // This helper still executes on CPU-only call sites, so we lean on the shared GpuUInt128 ladder until + // the TODO above lands and gives the CPU path its own Montgomery struct. + GpuUInt128 modulusGpu = new(modulus); + GpuUInt128 baseValue = new(2UL); + if (baseValue.CompareTo(modulusGpu) >= 0) + { + baseValue.Sub(modulusGpu); + } - // EvenPerfectBitScanner CPU exponents exceed Pow2WindowFallbackThreshold once p >= 138,000,000, - // so the single-bit fallback never triggers during production scans. Leave it commented out for targeted benchmarks. - // if (ShouldUseSingleBit(exponent)) - // { - // GpuUInt128 singleBitResult = Pow2MontgomeryModSingleBit(exponent, modulusGpu, baseValue); - // return (UInt128)singleBitResult; - // } + // EvenPerfectBitScanner CPU exponents exceed Pow2WindowFallbackThreshold once p >= 138,000,000, + // so the single-bit fallback never triggers during production scans. Leave it commented out for targeted benchmarks. + // if (ShouldUseSingleBit(exponent)) + // { + // GpuUInt128 singleBitResult = Pow2MontgomeryModSingleBit(exponent, modulusGpu, baseValue); + // return (UInt128)singleBitResult; + // } - GpuUInt128 exponentGpu = new(exponent); - int bitLength = exponentGpu.GetBitLength(); - int windowSize = GetWindowSize(bitLength); - int oddPowerCount = 1 << (windowSize - 1); + GpuUInt128 exponentGpu = new(exponent); + int bitLength = exponentGpu.GetBitLength(); + int windowSize = GetWindowSize(bitLength); + int oddPowerCount = 1 << (windowSize - 1); - Span oddPowers = stackalloc GpuUInt128[oddPowerCount]; - InitializeOddPowers(baseValue, modulusGpu, oddPowers); + Span oddPowers = stackalloc GpuUInt128[oddPowerCount]; + InitializeOddPowers(baseValue, modulusGpu, oddPowers); - GpuUInt128 result = GpuUInt128.One; - int index = bitLength - 1; + GpuUInt128 result = GpuUInt128.One; + int index = bitLength - 1; - while (index >= 0) - { - if (!IsBitSet(exponentGpu, index)) - { - result.MulMod(result, modulusGpu); - index--; - continue; - } + while (index >= 0) + { + if (!IsBitSet(exponentGpu, index)) + { + result.MulMod(result, modulusGpu); + index--; + continue; + } - int windowStart = index - windowSize + 1; - if (windowStart < 0) - { - windowStart = 0; - } + int windowStart = index - windowSize + 1; + if (windowStart < 0) + { + windowStart = 0; + } - while (!IsBitSet(exponentGpu, windowStart)) - { - windowStart++; - } + while (!IsBitSet(exponentGpu, windowStart)) + { + windowStart++; + } - int windowBitCount = index - windowStart + 1; - for (int square = 0; square < windowBitCount; square++) - { - result.MulMod(result, modulusGpu); - } + int windowBitCount = index - windowStart + 1; + for (int square = 0; square < windowBitCount; square++) + { + result.MulMod(result, modulusGpu); + } - ulong windowValue = ExtractWindowValue(exponentGpu, windowStart, windowBitCount); - int tableIndex = (int)((windowValue - 1UL) >> 1); - GpuUInt128 factor = oddPowers[tableIndex]; - result.MulMod(factor, modulusGpu); + ulong windowValue = ExtractWindowValue(exponentGpu, windowStart, windowBitCount); + int tableIndex = (int)((windowValue - 1UL) >> 1); + GpuUInt128 factor = oddPowers[tableIndex]; + result.MulMod(factor, modulusGpu); - index = windowStart - 1; - } + index = windowStart - 1; + } - return (UInt128)result; - } + return (UInt128)result; + } - /// - /// Uses the known cycle length to reduce the exponent and reuse cached remainders before - /// running the windowed pow2 ladder. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 Pow2MontgomeryModWindowedWithCycle(this UInt128 exponent, UInt128 modulus, ulong cycleLength, UInt128? precomputedRemainder = null) - { - // The EvenPerfectBitScanner CPU path receives cycle lengths from MersenneDivisorCycles, which never - // produces a zero-length order for admissible divisors. Keep the fallback documented for tests. - // if (cycleLength == 0UL) - // { - // return exponent.Pow2MontgomeryModWindowed(modulus); - // } + /// + /// Uses the known cycle length to reduce the exponent and reuse cached remainders before + /// running the windowed pow2 ladder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 Pow2MontgomeryModWindowedWithCycle(this UInt128 exponent, UInt128 modulus, ulong cycleLength, UInt128? precomputedRemainder = null) + { + // The EvenPerfectBitScanner CPU path receives cycle lengths from MersenneDivisorCycles, which never + // produces a zero-length order for admissible divisors. Keep the fallback documented for tests. + // if (cycleLength == 0UL) + // { + // return exponent.Pow2MontgomeryModWindowed(modulus); + // } - UInt128 remainder; - if (precomputedRemainder.HasValue) - { - // PowModWithCycle caches the reduced rotation count before reaching this helper, so production - // scans always reuse the precomputed remainder instead of recomputing the modulus here. - remainder = precomputedRemainder.Value; - } - else if (cycleLength != 0UL && exponent >= cycleLength) - { - if ((exponent >> 64) == UInt128.Zero) - { - remainder = (UInt128)((ulong)exponent % cycleLength); - } - else - { - remainder = exponent % (UInt128)cycleLength; - } - } - else - { - remainder = exponent; - } + UInt128 remainder; + if (precomputedRemainder.HasValue) + { + // PowModWithCycle caches the reduced rotation count before reaching this helper, so production + // scans always reuse the precomputed remainder instead of recomputing the modulus here. + remainder = precomputedRemainder.Value; + } + else if (cycleLength != 0UL && exponent >= cycleLength) + { + if ((exponent >> 64) == UInt128.Zero) + { + remainder = (UInt128)((ulong)exponent % cycleLength); + } + else + { + remainder = exponent % (UInt128)cycleLength; + } + } + else + { + remainder = exponent; + } - // When the cycle divides the exponent, 2^exponent ≡ 1 (mod modulus) and the caller expects an - // immediate success signal. - if (remainder == UInt128.Zero) - { - return UInt128.One; - } + // When the cycle divides the exponent, 2^exponent ≡ 1 (mod modulus) and the caller expects an + // immediate success signal. + if (remainder == UInt128.Zero) + { + return UInt128.One; + } - return remainder.Pow2MontgomeryModWindowed(modulus); - } + return remainder.Pow2MontgomeryModWindowed(modulus); + } - /// - /// Uses the known cycle length to reduce the exponent and reuse cached remainders before - /// running the windowed pow2 ladder. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 Pow2MontgomeryModWindowedWithCycle(this UInt128 exponent, UInt128 modulus, UInt128 cycleLength, UInt128? precomputedRemainder = null) - { - // Divisor-cycle lookups never hand the EvenPerfectBitScanner CPU flow a zero cycle length. Keep the - // fallback documented for targeted benchmarks and tests. - // if (cycleLength == UInt128.Zero) - // { - // return exponent.Pow2MontgomeryModWindowed(modulus); - // } + /// + /// Uses the known cycle length to reduce the exponent and reuse cached remainders before + /// running the windowed pow2 ladder. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 Pow2MontgomeryModWindowedWithCycle(this UInt128 exponent, UInt128 modulus, UInt128 cycleLength, UInt128? precomputedRemainder = null) + { + // Divisor-cycle lookups never hand the EvenPerfectBitScanner CPU flow a zero cycle length. Keep the + // fallback documented for targeted benchmarks and tests. + // if (cycleLength == UInt128.Zero) + // { + // return exponent.Pow2MontgomeryModWindowed(modulus); + // } - UInt128 remainder; - if (precomputedRemainder.HasValue) - { - // PowModWithCycle forwards the cached rotation count, so production scans always reuse the - // supplied remainder instead of recomputing exponent % cycleLength. - remainder = precomputedRemainder.Value; - } - else if (cycleLength != UInt128.Zero && exponent >= cycleLength) - { - remainder = exponent % cycleLength; - } - else - { - remainder = exponent; - } + UInt128 remainder; + if (precomputedRemainder.HasValue) + { + // PowModWithCycle forwards the cached rotation count, so production scans always reuse the + // supplied remainder instead of recomputing exponent % cycleLength. + remainder = precomputedRemainder.Value; + } + else if (cycleLength != UInt128.Zero && exponent >= cycleLength) + { + remainder = exponent % cycleLength; + } + else + { + remainder = exponent; + } - // A zero remainder means the divisor order divides the exponent, so 2^exponent ≡ 1 (mod modulus) - // and the sieves rely on the early exit. - if (remainder == UInt128.Zero) - { - return UInt128.One; - } + // A zero remainder means the divisor order divides the exponent, so 2^exponent ≡ 1 (mod modulus) + // and the sieves rely on the early exit. + if (remainder == UInt128.Zero) + { + return UInt128.One; + } - return remainder.Pow2MontgomeryModWindowed(modulus); - } + return remainder.Pow2MontgomeryModWindowed(modulus); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool ShouldUseSingleBit(UInt128 exponent) => (exponent >> 64) == UInt128.Zero && (ulong)exponent <= Pow2WindowFallbackThreshold; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ShouldUseSingleBit(UInt128 exponent) => (exponent >> 64) == UInt128.Zero && (ulong)exponent <= Pow2WindowFallbackThreshold; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetWindowSize(int bitLength) - { - if (bitLength <= Pow2WindowSize) - { - return Math.Max(bitLength, 1); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetWindowSize(int bitLength) + { + if (bitLength <= Pow2WindowSize) + { + return Math.Max(bitLength, 1); + } - if (bitLength <= 23) - { - return 4; - } + if (bitLength <= 23) + { + return 4; + } - if (bitLength <= 79) - { - return 5; - } + if (bitLength <= 79) + { + return 5; + } - if (bitLength <= 239) - { - return 6; - } + if (bitLength <= 239) + { + return 6; + } - if (bitLength <= 671) - { - return 7; - } + if (bitLength <= 671) + { + return 7; + } - return Pow2WindowSize; - } + return Pow2WindowSize; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void InitializeOddPowers(GpuUInt128 baseValue, GpuUInt128 modulus, Span oddPowers) - { - oddPowers[0] = baseValue; - if (oddPowers.Length == 1) - { - return; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void InitializeOddPowers(GpuUInt128 baseValue, GpuUInt128 modulus, Span oddPowers) + { + oddPowers[0] = baseValue; + if (oddPowers.Length == 1) + { + return; + } - // Reusing baseValue to hold base^2 for the shared odd-power ladder, just like the GPU helper. - baseValue.MulMod(baseValue, modulus); - for (int i = 1; i < oddPowers.Length; i++) - { - oddPowers[i] = oddPowers[i - 1]; - oddPowers[i].MulMod(baseValue, modulus); - } - } + // Reusing baseValue to hold base^2 for the shared odd-power ladder, just like the GPU helper. + baseValue.MulMod(baseValue, modulus); + for (int i = 1; i < oddPowers.Length; i++) + { + oddPowers[i] = oddPowers[i - 1]; + oddPowers[i].MulMod(baseValue, modulus); + } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static GpuUInt128 Pow2MontgomeryModSingleBit(UInt128 exponent, GpuUInt128 modulus, GpuUInt128 baseValue) - { - GpuUInt128 result = GpuUInt128.One; - GpuUInt128 remainingExponent = new(exponent); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static GpuUInt128 Pow2MontgomeryModSingleBit(UInt128 exponent, GpuUInt128 modulus, GpuUInt128 baseValue) + { + GpuUInt128 result = GpuUInt128.One; + GpuUInt128 remainingExponent = new(exponent); - while (!remainingExponent.IsZero) - { - if ((remainingExponent.Low & 1UL) != 0UL) - { - result.MulMod(baseValue, modulus); - } + while (!remainingExponent.IsZero) + { + if ((remainingExponent.Low & 1UL) != 0UL) + { + result.MulMod(baseValue, modulus); + } - remainingExponent.ShiftRight(1); - if (remainingExponent.IsZero) - { - break; - } + remainingExponent.ShiftRight(1); + if (remainingExponent.IsZero) + { + break; + } - // Reusing baseValue to hold the squared base in-place between iterations. - baseValue.MulMod(baseValue, modulus); - } + // Reusing baseValue to hold the squared base in-place between iterations. + baseValue.MulMod(baseValue, modulus); + } - return result; - } + return result; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsBitSet(GpuUInt128 value, int bitIndex) - { - if (bitIndex >= 64) - { - return ((value.High >> (bitIndex - 64)) & 1UL) != 0UL; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsBitSet(GpuUInt128 value, int bitIndex) + { + if (bitIndex >= 64) + { + return ((value.High >> (bitIndex - 64)) & 1UL) != 0UL; + } - return ((value.Low >> bitIndex) & 1UL) != 0UL; - } + return ((value.Low >> bitIndex) & 1UL) != 0UL; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount) - { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount) + { ulong mask = (1UL << windowBitCount) - 1UL; - if (windowStart != 0) - { - exponent.ShiftRight(windowStart); - return exponent.Low & mask; - } + if (windowStart != 0) + { + exponent.ShiftRight(windowStart); + return exponent.Low & mask; + } - return exponent.Low & mask; - } + return exponent.Low & mask; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static UInt128 ReduceProductBitwise(ulong p3, ulong p2, ulong p1, ulong p0, UInt128 modulus) - { - UInt128 remainder = UInt128.Zero; - for (int bit = 63; bit >= 0; bit--) - { - remainder <<= 1; - remainder |= (UInt128)((p3 >> bit) & 1UL); - if (remainder >= modulus) - { - remainder -= modulus; - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static UInt128 ReduceProductBitwise(ulong p3, ulong p2, ulong p1, ulong p0, UInt128 modulus) + { + UInt128 remainder = UInt128.Zero; + for (int bit = 63; bit >= 0; bit--) + { + remainder <<= 1; + remainder |= (UInt128)((p3 >> bit) & 1UL); + if (remainder >= modulus) + { + remainder -= modulus; + } + } - for (int bit = 63; bit >= 0; bit--) - { - remainder <<= 1; - remainder |= (UInt128)((p2 >> bit) & 1UL); - if (remainder >= modulus) - { - remainder -= modulus; - } - } + for (int bit = 63; bit >= 0; bit--) + { + remainder <<= 1; + remainder |= (UInt128)((p2 >> bit) & 1UL); + if (remainder >= modulus) + { + remainder -= modulus; + } + } - for (int bit = 63; bit >= 0; bit--) - { - remainder <<= 1; - remainder |= (UInt128)((p1 >> bit) & 1UL); - if (remainder >= modulus) - { - remainder -= modulus; - } - } + for (int bit = 63; bit >= 0; bit--) + { + remainder <<= 1; + remainder |= (UInt128)((p1 >> bit) & 1UL); + if (remainder >= modulus) + { + remainder -= modulus; + } + } - for (int bit = 63; bit >= 0; bit--) - { - remainder <<= 1; - remainder |= (UInt128)((p0 >> bit) & 1UL); - if (remainder >= modulus) - { - remainder -= modulus; - } - } + for (int bit = 63; bit >= 0; bit--) + { + remainder <<= 1; + remainder |= (UInt128)((p0 >> bit) & 1UL); + if (remainder >= modulus) + { + remainder -= modulus; + } + } - return remainder; - } + return remainder; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void MultiplyFull(UInt128 left, UInt128 right, out ulong p3, out ulong p2, out ulong p1, out ulong p0) - { - ulong leftLow = (ulong)left; - ulong leftHigh = (ulong)(left >> 64); - ulong rightLow = (ulong)right; - ulong rightHigh = (ulong)(right >> 64); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void MultiplyFull(UInt128 left, UInt128 right, out ulong p3, out ulong p2, out ulong p1, out ulong p0) + { + ulong leftLow = (ulong)left; + ulong leftHigh = (ulong)(left >> 64); + ulong rightLow = (ulong)right; + ulong rightHigh = (ulong)(right >> 64); - var partial0 = Multiply64(leftLow, rightLow); - var partial1 = Multiply64(leftLow, rightHigh); - var partial2 = Multiply64(leftHigh, rightLow); - var partial3 = Multiply64(leftHigh, rightHigh); + var partial0 = Multiply64(leftLow, rightLow); + var partial1 = Multiply64(leftLow, rightHigh); + var partial2 = Multiply64(leftHigh, rightLow); + var partial3 = Multiply64(leftHigh, rightHigh); - p0 = partial0.Low; - ulong carry = partial0.High; - ulong sum = carry + partial1.Low; - ulong carryMid = sum < partial1.Low ? 1UL : 0UL; - sum += partial2.Low; - if (sum < partial2.Low) - { - carryMid++; - } + p0 = partial0.Low; + ulong carry = partial0.High; + ulong sum = carry + partial1.Low; + ulong carryMid = sum < partial1.Low ? 1UL : 0UL; + sum += partial2.Low; + if (sum < partial2.Low) + { + carryMid++; + } - p1 = sum; - sum = partial1.High + partial2.High; - ulong carryHigh = sum < partial2.High ? 1UL : 0UL; - sum += partial3.Low; - if (sum < partial3.Low) - { - carryHigh++; - } + p1 = sum; + sum = partial1.High + partial2.High; + ulong carryHigh = sum < partial2.High ? 1UL : 0UL; + sum += partial3.Low; + if (sum < partial3.Low) + { + carryHigh++; + } - sum += carryMid; - if (sum < carryMid) - { - carryHigh++; - } + sum += carryMid; + if (sum < carryMid) + { + carryHigh++; + } - p2 = sum; - p3 = partial3.High + carryHigh; - } + p2 = sum; + p3 = partial3.High + carryHigh; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static (ulong High, ulong Low) Multiply64(ulong left, ulong right) - { - ulong a0 = (uint)left; - ulong a1 = left >> 32; - ulong b0 = (uint)right; - ulong b1 = right >> 32; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static (ulong High, ulong Low) Multiply64(ulong left, ulong right) + { + ulong a0 = (uint)left; + ulong a1 = left >> 32; + ulong b0 = (uint)right; + ulong b1 = right >> 32; - ulong lo = a0 * b0; - ulong mid1 = a1 * b0; - b0 = a0 * b1; - b1 *= a1; + ulong lo = a0 * b0; + ulong mid1 = a1 * b0; + b0 = a0 * b1; + b1 *= a1; - a0 = (lo >> 32) + (uint)mid1 + (uint)b0; - a1 = (lo & 0xFFFFFFFFUL) | (a0 << 32); - b1 += (mid1 >> 32) + (b0 >> 32) + (a0 >> 32); + a0 = (lo >> 32) + (uint)mid1 + (uint)b0; + a1 = (lo & 0xFFFFFFFFUL) | (a0 << 32); + b1 += (mid1 >> 32) + (b0 >> 32) + (a0 >> 32); - return (b1, a1); - } + return (b1, a1); + } - public static ulong CalculateOrder(this UInt128 q) - { - if (q <= UInt128Numbers.Two) - { - return 0UL; - } + public static ulong CalculateOrder(this UInt128 q, PrimeOrderCalculatorAccelerator gpu) + { + if (q <= UInt128Numbers.Two) + { + return 0UL; + } - UInt128 one = UInt128.One; - UInt128 phi = q - one; - if (phi > ulong.MaxValue) - { - throw new NotImplementedException("Such big values are not yet supported"); - } + UInt128 one = UInt128.One; + UInt128 phi = q - one; + if (phi > ulong.MaxValue) + { + throw new NotImplementedException("Such big values are not yet supported"); + } - ulong order = (ulong)phi; - uint[] smallPrimes = PrimesGenerator.SmallPrimes; - ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2; + ulong order = (ulong)phi; + uint[] smallPrimes = PrimesGenerator.SmallPrimes; + ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2; - int i = 0, primesLength = smallPrimes.Length; - UInt128 cycle = MersenneDivisorCycles.GetCycle(q); - // TODO: If the cache lacks this cycle, immediately schedule the configured device - // (GPU by default) to compute it on the fly and skip inserting it into the cache so - // wide-order factoring can still leverage cycle stepping without breaching the - // memory cap or introducing extra synchronization. - ulong prime, temp; - for (; i < primesLength; i++) - { - if (smallPrimesPow2[i] > order) - { - break; - } + int i = 0, primesLength = smallPrimes.Length; + UInt128 cycle = MersenneDivisorCycles.GetCycle(gpu, q); + // TODO: If the cache lacks this cycle, immediately schedule the configured device + // (GPU by default) to compute it on the fly and skip inserting it into the cache so + // wide-order factoring can still leverage cycle stepping without breaching the + // memory cap or introducing extra synchronization. + ulong prime, temp; + for (; i < primesLength; i++) + { + if (smallPrimesPow2[i] > order) + { + break; + } - prime = smallPrimes[i]; - while (order % prime == 0UL) - { - temp = order / prime; - if (temp.PowModWithCycle(q, cycle) == one) - { - order = temp; - } - else - { - break; - } - } - } + prime = smallPrimes[i]; + while (order % prime == 0UL) + { + temp = order / prime; + if (temp.PowModWithCycle(q, cycle) == one) + { + order = temp; + } + else + { + break; + } + } + } - return order; - } + return order; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CountTrailingZeros(this UInt128 value) - { - ulong valuePart = (ulong)value; - if (valuePart != 0UL) - { - return BitOperations.TrailingZeroCount(valuePart); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CountTrailingZeros(this UInt128 value) + { + ulong valuePart = (ulong)value; + if (valuePart != 0UL) + { + return BitOperations.TrailingZeroCount(valuePart); + } - return 64 + BitOperations.TrailingZeroCount((ulong)(value >> 64)); - } + return 64 + BitOperations.TrailingZeroCount((ulong)(value >> 64)); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPrimeCandidate(this UInt128 n) - { - UInt128 p, zero = UInt128.Zero; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPrimeCandidate(this UInt128 n) + { + UInt128 p, zero = UInt128.Zero; - uint[] smallPrimes = PrimesGenerator.SmallPrimes; - ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2; - ulong i, smallPrimesCount = (ulong)smallPrimes.Length; - for (i = 0UL; i < smallPrimesCount; i++) - { - if (smallPrimesPow2[i] > n) - { - break; - } + uint[] smallPrimes = PrimesGenerator.SmallPrimes; + ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2; + ulong i, smallPrimesCount = (ulong)smallPrimes.Length; + for (i = 0UL; i < smallPrimesCount; i++) + { + if (smallPrimesPow2[i] > n) + { + break; + } - p = smallPrimes[i]; - // TODO: Replace this direct `%` test with the shared divisor-cycle filter once the - // UInt128 path is wired into the cached cycle tables so wide candidates skip the slow - // modulo checks during primality pre-filtering. - if (n % p == zero) - { - return n == p; - } - } + p = smallPrimes[i]; + // TODO: Replace this direct `%` test with the shared divisor-cycle filter once the + // UInt128 path is wired into the cached cycle tables so wide candidates skip the slow + // modulo checks during primality pre-filtering. + if (n % p == zero) + { + return n == p; + } + } - return true; - } + return true; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod3(this UInt128 value) - { - // TODO: Fold these reductions into the multiply-high trick captured in Mod3BenchmarkResults so - // the UInt128 modulo helpers avoid `%` altogether and align with the faster CPU/GPU residue filters. - ulong remainder = ((ulong)value) % 3UL + ((ulong)(value >> 64)) % 3UL; - return remainder >= 3UL ? remainder - 3UL : remainder; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod3(this UInt128 value) + { + // TODO: Fold these reductions into the multiply-high trick captured in Mod3BenchmarkResults so + // the UInt128 modulo helpers avoid `%` altogether and align with the faster CPU/GPU residue filters. + ulong remainder = ((ulong)value) % 3UL + ((ulong)(value >> 64)) % 3UL; + return remainder >= 3UL ? remainder - 3UL : remainder; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod5(this UInt128 value) - { - // TODO: Replace the `% 5` operations with the precomputed multiply-high constants from the Mod5 - // benchmarks so the UInt128 path matches the 64-bit helpers without extra modulo instructions. - ulong remainder = (((ulong)value) % 5UL) + (((ulong)(value >> 64)) % 5UL); - return remainder >= 5UL ? remainder - 5UL : remainder; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod5(this UInt128 value) + { + // TODO: Replace the `% 5` operations with the precomputed multiply-high constants from the Mod5 + // benchmarks so the UInt128 path matches the 64-bit helpers without extra modulo instructions. + ulong remainder = (((ulong)value) % 5UL) + (((ulong)(value >> 64)) % 5UL); + return remainder >= 5UL ? remainder - 5UL : remainder; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod6(this UInt128 value) => ((value.Mod3() << 1) | ((ulong)value & 1UL)) switch - { - 0UL => 0UL, - 1UL => 3UL, - 2UL => 4UL, - 3UL => 1UL, - 4UL => 2UL, - _ => 5UL, - }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod6(this UInt128 value) => ((value.Mod3() << 1) | ((ulong)value & 1UL)) switch + { + 0UL => 0UL, + 1UL => 3UL, + 2UL => 4UL, + 3UL => 1UL, + 4UL => 2UL, + _ => 5UL, + }; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod7(this UInt128 value) - { - // TODO: Inline the lookup-based Mod7 reducer validated in the residue benchmarks so this helper - // stops relying on `% 7` in hot loops and mirrors the optimized CPU cycle filters. - ulong low = (ulong)value % 7UL; - ulong high = (ulong)(value >> 64) % 7UL; - ulong remainder = low + (high * 2UL); - if (remainder >= 7UL) - { - remainder -= 7UL; - if (remainder >= 7UL) - { - remainder -= 7UL; - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod7(this UInt128 value) + { + // TODO: Inline the lookup-based Mod7 reducer validated in the residue benchmarks so this helper + // stops relying on `% 7` in hot loops and mirrors the optimized CPU cycle filters. + ulong low = (ulong)value % 7UL; + ulong high = (ulong)(value >> 64) % 7UL; + ulong remainder = low + (high * 2UL); + if (remainder >= 7UL) + { + remainder -= 7UL; + if (remainder >= 7UL) + { + remainder -= 7UL; + } + } - return remainder; - } + return remainder; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod8(this UInt128 value) => (ulong)value & 7UL; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod8(this UInt128 value) => (ulong)value & 7UL; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod10(this UInt128 value128) - { - // Split and fold under mod 10: 2^64 ≡ 6 (mod 10) - // TODO: Precompute the 2^64 ≡ 6 folding constants once so this path stops recomputing `% 10` - // on each half and instead uses the span-based lookup captured in Mod10BenchmarkResults. - ulong low = (ulong)value128; - ulong high = (ulong)(value128 >> 64); - return ((low % 10UL) + ((high % 10UL) * 6UL)) % 10UL; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod10(this UInt128 value128) + { + // Split and fold under mod 10: 2^64 ≡ 6 (mod 10) + // TODO: Precompute the 2^64 ≡ 6 folding constants once so this path stops recomputing `% 10` + // on each half and instead uses the span-based lookup captured in Mod10BenchmarkResults. + ulong low = (ulong)value128; + ulong high = (ulong)(value128 >> 64); + return ((low % 10UL) + ((high % 10UL) * 6UL)) % 10UL; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Mod10_8_5_3(this UInt128 value, out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3) - { - byte current; - uint byteSum = 0U; - mod8 = (ulong)value & 7UL; - UInt128 zero = UInt128.Zero; - do - { - current = (byte)value; - byteSum += current; - value >>= 8; - } - while (value != zero); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Mod10_8_5_3(this UInt128 value, out ulong mod10, out ulong mod8, out ulong mod5, out ulong mod3) + { + byte current; + uint byteSum = 0U; + mod8 = (ulong)value & 7UL; + UInt128 zero = UInt128.Zero; + do + { + current = (byte)value; + byteSum += current; + value >>= 8; + } + while (value != zero); - mod5 = byteSum.Mod5(); - // TODO: Collapse this Mod10 switch into the shared lookup table from the CLI benchmarks so we - // avoid the nested switch expressions once the pooled residue tables become available. - mod10 = (mod8 & 1UL) == 0UL - ? mod5 switch - { - 0U => 0UL, - 1U => 6UL, - 2U => 2UL, - 3U => 8UL, - _ => 4UL, - } - : mod5 switch - { - 0U => 5UL, - 1U => 1UL, - 2U => 7UL, - 3U => 3UL, - _ => 9UL, - }; + mod5 = byteSum.Mod5(); + // TODO: Collapse this Mod10 switch into the shared lookup table from the CLI benchmarks so we + // avoid the nested switch expressions once the pooled residue tables become available. + mod10 = (mod8 & 1UL) == 0UL + ? mod5 switch + { + 0U => 0UL, + 1U => 6UL, + 2U => 2UL, + 3U => 8UL, + _ => 4UL, + } + : mod5 switch + { + 0U => 5UL, + 1U => 1UL, + 2U => 7UL, + 3U => 3UL, + _ => 9UL, + }; - mod3 = byteSum % 3U; - } + mod3 = byteSum % 3U; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod11(this UInt128 value) - { - ulong remainder = (((ulong)value) % 11UL) + (((ulong)(value >> 64)) % 11UL) * 5UL; - while (remainder >= 11UL) - { - remainder -= 11UL; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod11(this UInt128 value) + { + ulong remainder = (((ulong)value) % 11UL) + (((ulong)(value >> 64)) % 11UL) * 5UL; + while (remainder >= 11UL) + { + remainder -= 11UL; + } - return remainder; - } + return remainder; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Mod128(this UInt128 value) => (ulong)value & 127UL; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Mod128(this UInt128 value) => (ulong)value & 127UL; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 ModPow(this UInt128 value, UInt128 exponent, UInt128 modulus) - { - UInt128 zero = UInt128.Zero; - UInt128 one = UInt128.One; - UInt128 result = UInt128.One; - value %= modulus; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 ModPow(this UInt128 value, UInt128 exponent, UInt128 modulus) + { + UInt128 zero = UInt128.Zero; + UInt128 one = UInt128.One; + UInt128 result = UInt128.One; + value %= modulus; - while (exponent != zero) - { - if ((exponent & one) != zero) - { - result = MulMod(result, value, modulus); - } + while (exponent != zero) + { + if ((exponent & one) != zero) + { + result = MulMod(result, value, modulus); + } - value = MulMod(value, value, modulus); - exponent >>= 1; - } + value = MulMod(value, value, modulus); + exponent >>= 1; + } - return result; - } + return result; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 Mul64(this UInt128 a, UInt128 b) - { - ulong aLow = (ulong)a; - ulong bLow = (ulong)b; - ulong bHigh = (ulong)(b >> 64); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 Mul64(this UInt128 a, UInt128 b) + { + ulong aLow = (ulong)a; + ulong bLow = (ulong)b; + ulong bHigh = (ulong)(b >> 64); - ulong low = aLow * bLow; + ulong low = aLow * bLow; - // Keep the high-word accumulation in locals so the JIT does not rebuild the - // expression tree around the shift. Mirroring the MulHigh layout lets RyuJIT - // keep the intermediate sum in registers instead of reloading the partial - // product from the stack. - ulong high = aLow.MulHighCpu(bLow); - high += aLow * bHigh; + // Keep the high-word accumulation in locals so the JIT does not rebuild the + // expression tree around the shift. Mirroring the MulHigh layout lets RyuJIT + // keep the intermediate sum in registers instead of reloading the partial + // product from the stack. + ulong high = aLow.MulHighCpu(bLow); + high += aLow * bHigh; - return ((UInt128)high << 64) | low; - } + return ((UInt128)high << 64) | low; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 MulMod(this UInt128 a, UInt128 b, UInt128 modulus) - { - if (modulus <= UInt128.One) - { - return UInt128.Zero; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 MulMod(this UInt128 a, UInt128 b, UInt128 modulus) + { + if (modulus <= UInt128.One) + { + return UInt128.Zero; + } - UInt128 left = a % modulus; - UInt128 right = b % modulus; - if (left == UInt128.Zero || right == UInt128.Zero) - { - return UInt128.Zero; - } + UInt128 left = a % modulus; + UInt128 right = b % modulus; + if (left == UInt128.Zero || right == UInt128.Zero) + { + return UInt128.Zero; + } - if ((modulus >> 64) == UInt128.Zero) - { - // Reusing left to hold the reduced product; this mirrors the inline UInt128 multiply - // that topped MulMod64Benchmarks for 64-bit moduli. - ulong modulus64 = (ulong)modulus; - left = (UInt128)(((UInt128)(ulong)left * (ulong)right) % modulus64); - return left; - } + if ((modulus >> 64) == UInt128.Zero) + { + // Reusing left to hold the reduced product; this mirrors the inline UInt128 multiply + // that topped MulMod64Benchmarks for 64-bit moduli. + ulong modulus64 = (ulong)modulus; + left = (UInt128)(((UInt128)(ulong)left * (ulong)right) % modulus64); + return left; + } - MultiplyFull(left, right, out var p3, out var p2, out var p1, out var p0); - // Reusing left to capture the reduced 128-bit remainder before returning. - left = ReduceProductBitwise(p3, p2, p1, p0, modulus); - return left; - } + MultiplyFull(left, right, out var p3, out var p2, out var p1, out var p0); + // Reusing left to capture the reduced 128-bit remainder before returning. + left = ReduceProductBitwise(p3, p2, p1, p0, modulus); + return left; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 AddMod(this UInt128 value, UInt128 addend, UInt128 modulus) - { - // All current CPU callers (Pollard Rho and the Mersenne residue tracker) only supply moduli - // of at least three, so leave this guard documented but disabled. - // if (modulus <= UInt128.One) - // { - // return UInt128.Zero; - // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 AddMod(this UInt128 value, UInt128 addend, UInt128 modulus) + { + // All current CPU callers (Pollard Rho and the Mersenne residue tracker) only supply moduli + // of at least three, so leave this guard documented but disabled. + // if (modulus <= UInt128.One) + // { + // return UInt128.Zero; + // } - // Both operands arrive pre-reduced on these paths, letting us skip the redundant folds the old - // helper performed. - // value %= modulus; - // addend %= modulus; - // Pollard Rho keeps its polynomial constant inside [1, modulus - 1], and the Mersenne pow-delta - // term never hits zero for odd moduli, leaving this branch inactive outside targeted tests. - // if (addend == UInt128.Zero) - // { - // return value; - // } + // Both operands arrive pre-reduced on these paths, letting us skip the redundant folds the old + // helper performed. + // value %= modulus; + // addend %= modulus; + // Pollard Rho keeps its polynomial constant inside [1, modulus - 1], and the Mersenne pow-delta + // term never hits zero for odd moduli, leaving this branch inactive outside targeted tests. + // if (addend == UInt128.Zero) + // { + // return value; + // } - UInt128 threshold = modulus - addend; - if (value >= threshold) - { - return value - threshold; - } + UInt128 threshold = modulus - addend; + if (value >= threshold) + { + return value - threshold; + } - UInt128 sum = value + addend; - // The sum stays below the modulus under the threshold guard above, so no wraparound occurs. - return sum; - } + UInt128 sum = value + addend; + // The sum stays below the modulus under the threshold guard above, so no wraparound occurs. + return sum; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 SubtractOneMod(this UInt128 value, UInt128 modulus) - { - // Only the Mersenne residue tracker calls this helper, and it never observes moduli below three, - // so leave this guard documented but inactive. - // if (modulus <= UInt128.One) - // { - // return UInt128.Zero; - // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 SubtractOneMod(this UInt128 value, UInt128 modulus) + { + // Only the Mersenne residue tracker calls this helper, and it never observes moduli below three, + // so leave this guard documented but inactive. + // if (modulus <= UInt128.One) + // { + // return UInt128.Zero; + // } - if (value == UInt128.Zero) - { - // Wrapping occurs when the prior AddMod call produced one, so step back to modulus - 1. - return modulus - UInt128.One; - } + if (value == UInt128.Zero) + { + // Wrapping occurs when the prior AddMod call produced one, so step back to modulus - 1. + return modulus - UInt128.One; + } - return value - UInt128.One; - } + return value - UInt128.One; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UInt128 Pow(this UInt128 value, ulong exponent) - { - UInt128 result = UInt128.One; - while (exponent != 0UL) - { - if ((exponent & 1UL) != 0UL) - { - result *= value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UInt128 Pow(this UInt128 value, ulong exponent) + { + UInt128 result = UInt128.One; + while (exponent != 0UL) + { + if ((exponent & 1UL) != 0UL) + { + result *= value; + } - value *= value; - exponent >>= 1; - } + value *= value; + exponent >>= 1; + } - return result; - } + return result; + } } diff --git a/PerfectNumbers.Core/ULongExtensions.Gpu.cs b/PerfectNumbers.Core/ULongExtensions.Gpu.cs index b509f1f..098a300 100644 --- a/PerfectNumbers.Core/ULongExtensions.Gpu.cs +++ b/PerfectNumbers.Core/ULongExtensions.Gpu.cs @@ -44,27 +44,23 @@ public static partial class ULongExtensions } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Pow2MontgomeryModWindowedKeepGpu(this ulong exponent, in MontgomeryDivisorData divisor) - => Pow2MontgomeryGpuCalculator.CalculateKeep(exponent, divisor); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Pow2MontgomeryModWindowedConvertGpu(this ulong exponent, in MontgomeryDivisorData divisor) - => Pow2MontgomeryGpuCalculator.CalculateConvert(exponent, divisor); + public static ulong Pow2MontgomeryModWindowedKeepGpu(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, in MontgomeryDivisorData divisor) + => Pow2MontgomeryGpuCalculator.CalculateKeep(gpu, exponent, divisor); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong Pow2MontgomeryModWindowedConvertGpu(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, in MontgomeryDivisorData divisor) => Pow2MontgomeryGpuCalculator.CalculateConvert(gpu, exponent, divisor); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor) + public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, ulong cycleLength, in MontgomeryDivisorData divisor) { ulong rotationCount = exponent % cycleLength; - return Pow2MontgomeryGpuCalculator.CalculateConvert(rotationCount, divisor); + return Pow2MontgomeryGpuCalculator.CalculateConvert(gpu, rotationCount, divisor); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Pow2MontgomeryModFromCycleRemainderGpu(this ulong reducedExponent, in MontgomeryDivisorData divisor) + public static ulong Pow2MontgomeryModFromCycleRemainderGpu(this ulong reducedExponent, PrimeOrderCalculatorAccelerator gpu, in MontgomeryDivisorData divisor) { - return Pow2MontgomeryGpuCalculator.CalculateConvert(reducedExponent, divisor); + return Pow2MontgomeryGpuCalculator.CalculateConvert(gpu, reducedExponent, divisor); } } diff --git a/PerfectNumbers.Core/ULongExtensions.cs b/PerfectNumbers.Core/ULongExtensions.cs index ce5bb40..86c4c35 100644 --- a/PerfectNumbers.Core/ULongExtensions.cs +++ b/PerfectNumbers.Core/ULongExtensions.cs @@ -1,5 +1,6 @@ using System.Numerics; using System.Runtime.CompilerServices; +using PerfectNumbers.Core.Gpu.Accelerators; namespace PerfectNumbers.Core; @@ -98,7 +99,7 @@ public static partial class ULongExtensions } } - public static ulong CalculateOrder(this ulong q) + public static ulong CalculateOrder(this ulong q, PrimeOrderCalculatorAccelerator gpu) { if (q <= 2UL) { @@ -111,7 +112,7 @@ public static partial class ULongExtensions int i = 0, primesLength = smallPrimes.Length; UInt128 q128 = q, - cycle = MersenneDivisorCycles.GetCycle(q128); + cycle = MersenneDivisorCycles.GetCycle(gpu, q128); // TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand // GPU computation (respecting the configured device) without promoting the result into // the cache so the order calculator still benefits from cycle stepping while keeping the @@ -860,7 +861,7 @@ public static partial class ULongExtensions /// Computes 2^exponent mod modulus using iterative CRT composition from mod 10 up to modulus. /// Only for modulus >= 10 and reasonable size. /// - public static UInt128 PowModCrt(this ulong exponent, UInt128 modulus, MersenneDivisorCycles cycles) + public static UInt128 PowModCrt(this ulong exponent, PrimeOrderCalculatorAccelerator gpu, UInt128 modulus, MersenneDivisorCycles cycles) { if (modulus < 10) return PowMod(exponent, modulus); // fallback to classic @@ -875,7 +876,7 @@ public static partial class ULongExtensions for (; modulusCandidate <= modulus; modulusCandidate++) { - cycle = MersenneDivisorCycles.GetCycle(modulusCandidate); + cycle = MersenneDivisorCycles.GetCycle(gpu, modulusCandidate); remainderForCandidate = cycle > zero ? PowModWithCycle(exponent, modulusCandidate, cycle) : PowMod(exponent, modulusCandidate); @@ -935,7 +936,7 @@ public static partial class ULongExtensions } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool SharesFactorWithExponentMinusOne(this ulong exponent) + public static bool SharesFactorWithExponentMinusOne(this ulong exponent, PrimeOrderCalculatorAccelerator gpu) { ulong prime, value = exponent - 1UL; value >>= BitOperations.TrailingZeroCount(value); @@ -951,7 +952,7 @@ public static partial class ULongExtensions continue; } - if (exponent % prime.CalculateOrder() == 0UL) + if (exponent % prime.CalculateOrder(gpu) == 0UL) { return true; } @@ -963,7 +964,7 @@ public static partial class ULongExtensions while (value % prime == 0UL); } - if (value > 1UL && exponent % value.CalculateOrder() == 0UL) + if (value > 1UL && exponent % value.CalculateOrder(gpu) == 0UL) { return true; }