Compare commits

..
65 changed files with 3538 additions and 4616 deletions

No files matched your search

-5
View File
@@ -106,16 +106,11 @@ There is only one agents file - this one. Don't search for another one.
Always use indentation with four space characters in source code files, even if a file currently uses tabs or appears to rely on eight-space indentation. Increase indentation level by exactly one indentation unit (four spaces) per nested block. When parameters or long expressions wrap, indent the continuation with two indentation units for clarity.
Explicitly prefer reusing existing variables once their previous values are no longer required instead of declaring new locals of the same type. Call out the variable reuse in comments when its meaning changes so the intent stays clear and register pressure remains minimal.
Do not introduce try/finally blocks solely to release pooled resources. Restructure the code so that every return path releases the resources without relying on finally unless the control flow would become unclear or unmaintainable.
Keep the divisor cycle cache limited to a single block in memory (only the on-disk snapshot). When a lookup misses, compute the required cycle on the configured device and discard the transient result instead of scheduling background generation or caching additional blocks.
All code, comments, commit messages, branch names and PR descriptions must be written in American English.
- When modifying existing files, preserve all relevant TODO notes and explanatory comments unless they are no longer valid.
- Prefer defining data holders as structs or classes with public fields over auto-properties when performance on the hot path is a concern.
- Do not add or keep methods that simply wrap another method call without adding any logic; inline those calls or extend the wrapper with meaningful work.
## Performance implementation checklist
- Prefer dedicated kernels over flag-controlled or parameterized kernels. Avoid conditional branches in kernels and tight loops by creating separate kernels when necessary.
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core.Gpu;
@@ -49,7 +50,7 @@ public class GpuPow2ModBenchmarks
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
}
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, GpuUInt128 modulus)
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, in GpuUInt128 modulus)
{
if (modulus.IsZero || modulus == GpuUInt128.One)
{
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core.Gpu;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core.Gpu;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core.Gpu;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core.Gpu;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core.Gpu;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core;
namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
@@ -1,7 +1,9 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System.Numerics;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
namespace EvenPerfectBitScanner.Benchmarks;
@@ -50,31 +52,14 @@ public class Pow2MontgomeryModBenchmarks
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
/// </summary>
[Benchmark(Baseline = true)]
public ulong BaselineCpu()
public ulong Baseline()
{
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);
}
return checksum;
}
/// <summary>
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
/// </summary>
[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].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
}
return checksum;
@@ -84,31 +69,14 @@ public class Pow2MontgomeryModBenchmarks
/// 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).
/// </summary>
[Benchmark]
public ulong BaselineWithKnownCycleCpu()
public ulong BaselineWithKnownCycle()
{
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]);
}
return checksum;
}
/// <summary>
/// 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).
/// </summary>
[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]);
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
}
return checksum;
@@ -1,4 +1,6 @@
using System;
using System.Buffers.Binary;
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core;
@@ -20,391 +22,320 @@ namespace EvenPerfectBitScanner.Benchmarks;
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 5)]
public class Pow2MontgomeryModCycleComputationBenchmarks
{
private const int SampleCount = 256;
private const ulong OneShiftedLeft63 = 1UL << 63;
private const long OneShiftedLeft60 = 1L << 60;
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
private readonly MontgomeryDivisorData[] _veryLargeDivisors = new MontgomeryDivisorData[SampleCount];
private readonly UInt128[] _wideModuli = new UInt128[SampleCount];
private readonly ulong[] _smallExponents = new ulong[SampleCount];
private readonly ulong[] _largeExponents = new ulong[SampleCount];
private readonly ulong[] _veryLargeExponents = new ulong[SampleCount];
private readonly ulong[] _smallCycles = new ulong[SampleCount];
private readonly ulong[] _largeCycles = new ulong[SampleCount];
private readonly ulong[] _veryLargeCycles = new ulong[SampleCount];
private readonly UInt128[] _wideCycles = new UInt128[SampleCount];
private const int SampleCount = 256;
private const ulong OneShiftedLeft63 = 1UL << 63;
private const long OneShiftedLeft60 = 1L << 60;
private readonly MontgomeryDivisorData[] _smallDivisors = new MontgomeryDivisorData[SampleCount];
private readonly MontgomeryDivisorData[] _largeDivisors = new MontgomeryDivisorData[SampleCount];
private readonly MontgomeryDivisorData[] _veryLargeDivisors = new MontgomeryDivisorData[SampleCount];
private readonly UInt128[] _wideModuli = new UInt128[SampleCount];
private readonly ulong[] _smallExponents = new ulong[SampleCount];
private readonly ulong[] _largeExponents = new ulong[SampleCount];
private readonly ulong[] _veryLargeExponents = new ulong[SampleCount];
private readonly ulong[] _smallCycles = new ulong[SampleCount];
private readonly ulong[] _largeCycles = new ulong[SampleCount];
private readonly ulong[] _veryLargeCycles = new ulong[SampleCount];
private readonly UInt128[] _wideCycles = new UInt128[SampleCount];
private readonly Random _random = new(113);
private ulong? _previousPrimeOrder;
private UInt128? _previousWidePrimeOrder;
private readonly Random _random = new(113);
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()
{
for (int i = 0; i < SampleCount; i++)
{
[GlobalSetup]
public void Setup()
{
for (int i = 0; i < SampleCount; i++)
{
#if DEBUG
Console.WriteLine($"Generating sample {i}");
Console.WriteLine($"Generating sample {i}");
#endif
ulong smallModulus = NextSmallOddModulus();
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
_smallExponents[i] = NextSmallExponent();
_smallCycles[i] = CalculateCycleLengthWithHeuristics(smallModulus);
ulong smallModulus = NextSmallOddModulus();
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
_smallExponents[i] = NextSmallExponent();
_smallCycles[i] = CalculateCycleLengthWithHeuristics(smallModulus);
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
_largeExponents[i] = NextLargeExponent();
_largeCycles[i] = CalculateCycleLengthWithHeuristics(largeModulus, largeCycle);
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
_largeExponents[i] = NextLargeExponent();
_largeCycles[i] = CalculateCycleLengthWithHeuristics(largeModulus, largeCycle);
ulong veryLargeModulus = NextVeryLargeOddModulus();
_veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus);
_veryLargeExponents[i] = NextVeryLargeExponent();
ulong veryLargeModulus = NextVeryLargeOddModulus();
_veryLargeDivisors[i] = CreateMontgomeryDivisorData(veryLargeModulus);
_veryLargeExponents[i] = NextVeryLargeExponent();
#if DEBUG
Console.WriteLine($"Calculating cycle length {veryLargeModulus}");
Console.WriteLine($"Calculating cycle length {veryLargeModulus}");
#endif
_veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus);
_veryLargeCycles[i] = CalculateCycleLengthWithHeuristics(veryLargeModulus);
Console.WriteLine($"Generating wide sample {i}");
UInt128 wideModulus = NextWideOddModulus();
_wideModuli[i] = wideModulus;
_wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus);
}
}
Console.WriteLine($"Generating wide sample {i}");
UInt128 wideModulus = NextWideOddModulus();
_wideModuli[i] = wideModulus;
_wideCycles[i] = CalculateCycleLengthWithHeuristics(wideModulus);
}
}
/// <summary>
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
/// large set.
/// </summary>
// [Benchmark(Baseline = true)]
public ulong MontgomeryWithoutCycleCpu()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
ulong checksum = 0UL;
/// <summary>
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
/// large set.
/// </summary>
// [Benchmark(Baseline = true)]
public ulong MontgomeryWithoutCycle()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] 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].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
}
return checksum;
}
return checksum;
}
/// <summary>
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
/// large set.
/// </summary>
// [Benchmark(Baseline = true)]
public ulong MontgomeryWithoutCycleGpu()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
ulong checksum = 0UL;
/// <summary>
/// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
/// on the large one (0.08×).
/// </summary>
[Benchmark]
public ulong MontgomeryWithPrecomputedCycle()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
}
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
}
return checksum;
}
return checksum;
}
/// <summary>
/// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
/// on the large one (0.08×).
/// </summary>
[Benchmark]
public ulong MontgomeryWithPrecomputedCycleCpu()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
ulong checksum = 0UL;
/// <summary>
/// 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).
/// </summary>
// [Benchmark]
public ulong MontgomeryWithGpuCycleComputation()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
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++)
{
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
}
return checksum;
}
/// <summary>
/// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
/// on the large one (0.08×).
/// </summary>
[Benchmark]
public ulong MontgomeryWithPrecomputedCycleGpu()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
}
return checksum;
}
/// <summary>
/// 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).
/// </summary>
// [Benchmark]
public ulong MontgomeryWithGpuCycleComputationCpu()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] 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]);
}
return checksum;
}
/// <summary>
/// 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).
/// </summary>
// [Benchmark]
public ulong MontgomeryWithGpuCycleComputationGpu()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] 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]);
}
return checksum;
}
[Benchmark]
public ulong MontgomeryWithHeuristicCycleComputationCpu()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] 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]);
}
return checksum;
}
[Benchmark]
public ulong MontgomeryWithHeuristicCycleComputationGpu()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
ulong checksum = 0UL;
for (int i = 0; i < SampleCount; i++)
{
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
}
return checksum;
}
[Benchmark]
public UInt128 HeuristicWideCycleComputation()
{
UInt128 checksum = UInt128.Zero;
for (int i = 0; i < SampleCount; i++)
{
UInt128 cycle = CalculateCycleLengthWithHeuristics(_wideModuli[i]);
_wideCycles[i] = cycle;
checksum ^= cycle;
}
return checksum;
}
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);
}
}
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
private ulong NextLargeExponent() => (ulong)_random.NextInt64(OneShiftedLeft60, long.MaxValue);
private ulong NextVeryLargeExponent()
{
Span<byte> buffer = stackalloc byte[8];
_random.NextBytes(buffer);
ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63);
return value;
}
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 NextVeryLargeOddModulus()
{
Span<byte> buffer = stackalloc byte[8];
_random.NextBytes(buffer);
ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL;
return value;
}
private UInt128 NextWideOddModulus()
{
Span<byte> buffer = stackalloc byte[16];
_random.NextBytes(buffer);
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
Span<byte> highSlice = buffer.Slice(8, 8);
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(highSlice) | (1UL << 63);
UInt128 value = ((UInt128)high << 64) | low;
return value | UInt128.One;
}
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.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
return new MontgomeryDivisorData(
modulus,
nPrime,
montgomeryOne,
montgomeryTwo,
montgomeryTwoSquared);
}
return checksum;
}
private ulong CalculateCycleLengthWithHeuristics(ulong modulus, ulong fallbackOrder = 0UL)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
}
[Benchmark]
public ulong MontgomeryWithHeuristicCycleComputation()
{
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
ulong checksum = 0UL;
// When the below is uncommented, the benchmark never completes due to the primality test cost on very large random set.
for (int i = 0; i < SampleCount; i++)
{
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
}
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(modulus);
// // bool isPrime = PrimeTester.IsPrimeInternal(modulus, CancellationToken.None);
// if (!isPrime)
// {
// return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
// }
return checksum;
}
[Benchmark]
public UInt128 HeuristicWideCycleComputation()
{
UInt128 checksum = UInt128.Zero;
for (int i = 0; i < SampleCount; i++)
{
UInt128 cycle = CalculateCycleLengthWithHeuristics(_wideModuli[i]);
_wideCycles[i] = cycle;
checksum ^= cycle;
}
return checksum;
}
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);
}
}
private ulong NextSmallExponent() => (ulong)_random.NextInt64(1L, 1_000_000L);
private ulong NextLargeExponent() => (ulong)_random.NextInt64(OneShiftedLeft60, long.MaxValue);
private ulong NextVeryLargeExponent()
{
Span<byte> buffer = stackalloc byte[8];
_random.NextBytes(buffer);
ulong value = checked(BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63);
return value;
}
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 NextVeryLargeOddModulus()
{
Span<byte> buffer = stackalloc byte[8];
_random.NextBytes(buffer);
ulong value = BinaryPrimitives.ReadUInt64LittleEndian(buffer) | OneShiftedLeft63 | 1UL;
return value;
}
private UInt128 NextWideOddModulus()
{
Span<byte> buffer = stackalloc byte[16];
_random.NextBytes(buffer);
ulong low = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
Span<byte> highSlice = buffer.Slice(8, 8);
ulong high = BinaryPrimitives.ReadUInt64LittleEndian(highSlice) | (1UL << 63);
UInt128 value = ((UInt128)high << 64) | low;
return value | UInt128.One;
}
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.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
return new MontgomeryDivisorData(
modulus,
nPrime,
montgomeryOne,
montgomeryTwo,
montgomeryTwoSquared);
}
private ulong CalculateCycleLengthWithHeuristics(ulong modulus, ulong fallbackOrder = 0UL)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
{
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
}
// When the below is uncommented, the benchmark never completes due to the primality test cost on very large random set.
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(modulus);
// // bool isPrime = PrimeTester.IsPrimeInternal(modulus, CancellationToken.None);
// if (!isPrime)
// {
// return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
// }
#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.Gpu);
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
modulus,
_previousPrimeOrder,
divisorData,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
if (order != 0UL)
{
_previousPrimeOrder = order;
return order;
}
if (orderResult.Order != 0UL)
{
_previousPrimeOrder = orderResult.Order;
return orderResult.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(modulus, divisorData);
}
private UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus)
{
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
{
return MersenneDivisorCycles.GetCycle(modulus);
}
private UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus)
{
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
{
return MersenneDivisorCycles.GetCycle(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.Gpu);
PrimeOrderCalculator.PrimeOrderResultWide orderResult = PrimeOrderCalculator.Calculate(
modulus,
_previousWidePrimeOrder,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
if (order != UInt128.Zero)
{
_previousWidePrimeOrder = order;
return order;
}
if (orderResult.Order != UInt128.Zero)
{
_previousWidePrimeOrder = orderResult.Order;
return orderResult.Order;
}
_previousWidePrimeOrder = null;
_previousWidePrimeOrder = null;
Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation");
return MersenneDivisorCycles.GetCycle(modulus);
}
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 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);
}
}
@@ -1,4 +1,6 @@
using System;
using System.Buffers.Binary;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core;
@@ -174,7 +176,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
return (UInt128)result;
}
private static bool ShouldUseSingleBit(GpuUInt128 exponent)
private static bool ShouldUseSingleBit(in GpuUInt128 exponent)
{
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
{
@@ -255,7 +257,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
return result;
}
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
{
if (bitIndex >= 64)
{
@@ -265,7 +267,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
return ((value.Low >> bitIndex) & 1UL) != 0UL;
}
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
{
if (windowStart != 0)
{
@@ -1,4 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core.Gpu;
@@ -1,3 +1,5 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using PerfectNumbers.Core.Gpu;
namespace EvenPerfectBitScanner.Benchmarks;
+33 -294
View File
@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -44,11 +45,6 @@ internal static class Program
private static readonly Optimized PrimeIterator = new();
private static ulong _byDivisorStartPrime;
private static bool _testMode;
private static int _testTargetPrimeCount;
private static int _testProcessedPrimeCount;
private const string TestTimeFileName = "even_perfect_test_time.txt";
[ThreadStatic]
private static bool _lastCompositeP;
@@ -85,8 +81,6 @@ internal static class Program
int scanBatchSize = 2_097_152, sliceSize = 32;
ulong residueKMax = 5_000_000UL;
string filterFile = string.Empty;
UInt128 maxPrimeLimit = UInt128.MaxValue;
bool maxPrimeConfigured = false;
string cyclesPath = DefaultCyclesPath;
int cyclesBatchSize = 512;
bool continueCyclesGeneration = false;
@@ -99,10 +93,6 @@ internal static class Program
ulong remainder = 0UL;
bool startPrimeProvided = false;
_testMode = false;
_testTargetPrimeCount = 0;
_testProcessedPrimeCount = 0;
// NTT backend selection (GPU): reference vs staged
for (; argIndex < args.Length; argIndex++)
{
@@ -119,20 +109,6 @@ internal static class Program
currentP = ulong.Parse(arg.AsSpan(arg.IndexOf('=') + 1));
startPrimeProvided = true;
}
else if (arg.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase))
{
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
{
maxPrimeLimit = parsedMaxPrime;
maxPrimeConfigured = true;
}
else
{
Console.WriteLine("Invalid value for --max-prime.");
return;
}
}
else if (arg.Equals("--increment=bit", StringComparison.OrdinalIgnoreCase))
{
useBitTransform = true;
@@ -209,10 +185,6 @@ internal static class Program
{
useModuloWorkaround = true;
}
else if (arg.Equals("--test", StringComparison.OrdinalIgnoreCase))
{
_testMode = true;
}
else if (arg.Equals("--use-order", StringComparison.OrdinalIgnoreCase))
{
useOrder = true;
@@ -405,19 +377,6 @@ internal static class Program
}
}
int testPrimeCandidateLimit = 0;
if (_testMode)
{
currentP = 31UL;
remainder = currentP % 6UL;
filterFile = string.Empty;
maxPrimeLimit = UInt128.MaxValue;
maxPrimeConfigured = false;
startPrimeProvided = true;
testPrimeCandidateLimit = Math.Max(1, threadCount) * 3;
}
if (useByDivisor)
{
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
@@ -427,7 +386,7 @@ internal static class Program
_byDivisorStartPrime = 0UL;
}
if (useByDivisor && !_testMode && string.IsNullOrEmpty(filterFile))
if (useByDivisor && string.IsNullOrEmpty(filterFile))
{
Console.WriteLine("--mersenne=bydivisor requires --filter-p=<path>.");
return;
@@ -556,7 +515,9 @@ internal static class Program
{
_byDivisorTester = mersenneOnGpu
? new MersenneNumberDivisorByDivisorGpuTester()
: new MersenneNumberDivisorByDivisorCpuTester();
: new MersenneNumberDivisorByDivisorCpuTester(
useGpuPow2Mod: !GpuContextPool.ForceCpu,
useGpuCycleComputation: orderOnGpu);
_byDivisorTester.BatchSize = scanBatchSize;
}
@@ -645,102 +606,51 @@ internal static class Program
File.WriteAllText(ResultsFileName, $"p,searchedMersenne,detailedCheck,passedAllTests{Environment.NewLine}");
}
bool useFilter = !_testMode && !string.IsNullOrEmpty(filterFile);
bool useFilter = !string.IsNullOrEmpty(filterFile);
HashSet<ulong> filter = [];
List<ulong> byDivisorCandidates = [];
ulong maxP = 0UL;
ulong[] localFilter = Array.Empty<ulong>();
int filterCount = 0;
if (_useByDivisorMode)
if (useFilter)
{
if (_testMode)
Console.WriteLine("Loading filter...");
if (_useByDivisorMode)
{
byDivisorCandidates = BuildTestPrimeCandidates(testPrimeCandidateLimit);
byDivisorCandidates = LoadByDivisorCandidates(filterFile);
}
else
{
Console.WriteLine("Loading filter...");
int skippedByLimit = 0;
byDivisorCandidates = LoadByDivisorCandidates(filterFile, maxPrimeLimit, maxPrimeConfigured, out skippedByLimit);
if (maxPrimeConfigured)
// TODO: Rent this filter buffer from ArrayPool<ulong> and reuse it across reload batches so
// we do not allocate fresh arrays while replaying large result filters.
localFilter = new ulong[1024];
filterCount = 0;
LoadResultsFile(filterFile, (p, detailedCheck, passedAllTests) =>
{
if (skippedByLimit > 0)
if (passedAllTests)
{
Console.WriteLine($"Skipped {skippedByLimit} by-divisor candidate(s) above --max-prime limit.");
localFilter[filterCount++] = p;
if (p > maxP)
{
maxP = p;
}
if (filterCount == 1024)
{
filter.AddRange(localFilter[..filterCount]);
filterCount = 0;
Console.WriteLine($"Added {p}");
}
}
});
if (byDivisorCandidates.Count == 0)
{
Console.WriteLine("No by-divisor candidates fall within the --max-prime limit.");
return;
}
}
}
}
else if (useFilter)
{
Console.WriteLine("Loading filter...");
// TODO: Rent this filter buffer from ArrayPool<ulong> and reuse it across reload batches so
// we do not allocate fresh arrays while replaying large result filters.
localFilter = new ulong[1024];
filterCount = 0;
bool addedWithinLimit = false;
int skippedByLimit = 0;
LoadResultsFile(filterFile, (p, detailedCheck, passedAllTests) =>
{
if (!passedAllTests)
{
return;
}
if (maxPrimeConfigured && (UInt128)p > maxPrimeLimit)
{
skippedByLimit++;
return;
}
localFilter[filterCount++] = p;
addedWithinLimit = true;
if (p > maxP)
{
maxP = p;
}
if (filterCount == 1024)
if (filterCount > 0)
{
filter.AddRange(localFilter[..filterCount]);
filterCount = 0;
Console.WriteLine($"Added {p}");
}
});
if (filterCount > 0)
{
filter.AddRange(localFilter[..filterCount]);
}
if (maxPrimeConfigured)
{
if (!addedWithinLimit)
{
Console.WriteLine("No filter candidates fall within the --max-prime limit.");
return;
}
if (skippedByLimit > 0)
{
Console.WriteLine($"Skipped {skippedByLimit} filter candidate(s) above --max-prime limit.");
}
}
}
if (_testMode)
{
_testTargetPrimeCount = testPrimeCandidateLimit;
}
if (_primeCount >= 2)
{
_primeFoundAfterInit = true;
@@ -753,12 +663,6 @@ internal static class Program
// Configure batch size for GPU primality sieve
PrimeTester.GpuBatchSize = gpuPrimeBatch;
Stopwatch? stopwatch = null;
if (_testMode)
{
stopwatch = Stopwatch.StartNew();
}
Console.WriteLine("Starting scan...");
if (_useByDivisorMode)
@@ -766,12 +670,6 @@ internal static class Program
RunByDivisorMode(byDivisorCandidates, threadCount);
FlushBuffer();
StringBuilderPool.Return(_outputBuilder!);
if (stopwatch is not null)
{
stopwatch.Stop();
ReportTestTime(stopwatch.Elapsed);
}
return;
}
@@ -786,14 +684,7 @@ internal static class Program
}
}
if (_testMode)
{
testPrimeCandidateLimit = Math.Max(1, threadCount) * 3;
_testTargetPrimeCount = testPrimeCandidateLimit;
}
_state = ((long)currentP << 3) | (long)remainder;
Task[] tasks = new Task[threadCount];
int taskIndex = 0;
@@ -863,71 +754,13 @@ internal static class Program
}
Task.WaitAll(tasks);
TimeSpan testElapsed = default;
bool reportTestTime = false;
if (stopwatch is not null)
{
stopwatch.Stop();
testElapsed = stopwatch.Elapsed;
reportTestTime = true;
}
FlushBuffer();
StringBuilderPool.Return(_outputBuilder!);
if (reportTestTime)
{
ReportTestTime(testElapsed);
}
}
private static List<ulong> BuildTestPrimeCandidates(int targetCount)
{
if (targetCount <= 0)
{
return [];
}
List<ulong> candidates = new(targetCount);
ulong previous = 29UL;
int remaining = targetCount;
ulong nextPrime = 0UL;
while (remaining > 0)
{
try
{
nextPrime = PrimeIterator.Next(in previous);
}
catch (InvalidOperationException)
{
break;
}
if (nextPrime < 31UL)
{
previous = nextPrime;
continue;
}
candidates.Add(nextPrime);
remaining--;
previous = nextPrime;
}
if (remaining > 0)
{
Console.WriteLine("Unable to populate the requested number of test primes before reaching the 64-bit limit.");
}
return candidates;
}
private static List<ulong> LoadByDivisorCandidates(string candidateFile, UInt128 maxPrimeLimit, bool maxPrimeConfigured, out int skippedByLimit)
private static List<ulong> LoadByDivisorCandidates(string candidateFile)
{
List<ulong> candidates = [];
skippedByLimit = 0;
using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using StreamReader reader = new(readStream);
string? line;
@@ -967,14 +800,7 @@ internal static class Program
// arrive so by-divisor candidate loading avoids per-value string allocations.
if (ulong.TryParse(span[start..index], NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsed))
{
if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit)
{
candidates.Add(parsed);
}
else
{
skippedByLimit++;
}
candidates.Add(parsed);
}
}
}
@@ -1072,7 +898,6 @@ internal static class Program
Console.WriteLine();
Console.WriteLine("Options:");
Console.WriteLine(" --prime=<value> starting exponent (p)");
Console.WriteLine(" --max-prime=<value> inclusive upper bound for primes from filter files");
Console.WriteLine(" --increment=bit|add exponent increment method");
Console.WriteLine(" --threads=<value> number of worker threads");
Console.WriteLine(" --block-size=<value> values processed per thread batch");
@@ -1166,21 +991,6 @@ internal static class Program
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void OnPrimeCandidateConfirmed()
{
if (!_testMode)
{
return;
}
int processed = Interlocked.Increment(ref _testProcessedPrimeCount);
if (processed == _testTargetPrimeCount)
{
Volatile.Write(ref _limitReached, true);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryAcquireConsoleSlot(bool primeCandidate, out bool primeFlag)
{
@@ -1317,75 +1127,6 @@ internal static class Program
}
}
private static void ReportTestTime(TimeSpan elapsed)
{
double elapsedSeconds = elapsed.TotalSeconds;
double elapsedMilliseconds = elapsed.TotalMilliseconds;
Console.WriteLine($"Test elapsed time: {elapsedSeconds:F3} s");
string filePath = GetTestTimeFilePath();
bool hasPrevious = TryReadTestTime(filePath, out double previousMilliseconds);
string elapsedText = elapsedMilliseconds.ToString("F3", CultureInfo.InvariantCulture);
if (!hasPrevious)
{
WriteTestTime(filePath, elapsedMilliseconds);
Console.WriteLine($"FIRST TIME: {elapsedText} ms");
return;
}
string previousText = previousMilliseconds.ToString("F3", CultureInfo.InvariantCulture);
if (elapsedMilliseconds < previousMilliseconds)
{
WriteTestTime(filePath, elapsedMilliseconds);
Console.WriteLine($"BETTER TIME: {elapsedText} ms (previous {previousText} ms)");
}
else
{
Console.WriteLine($"WORSE TIME: {elapsedText} ms (best {previousText} ms)");
}
}
private static string GetTestTimeFilePath()
{
if (string.IsNullOrEmpty(_resultsDir))
{
return TestTimeFileName;
}
return Path.Combine(_resultsDir!, TestTimeFileName);
}
private static bool TryReadTestTime(string filePath, out double milliseconds)
{
if (!File.Exists(filePath))
{
milliseconds = 0.0;
return false;
}
string content = File.ReadAllText(filePath).Trim();
if (content.Length == 0)
{
milliseconds = 0.0;
return false;
}
return double.TryParse(content, NumberStyles.Float, CultureInfo.InvariantCulture, out milliseconds);
}
private static void WriteTestTime(string filePath, double milliseconds)
{
string? directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
string formatted = milliseconds.ToString("F6", CultureInfo.InvariantCulture);
File.WriteAllText(filePath, formatted);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ulong CountOnes(ulong value)
{
@@ -1616,8 +1357,6 @@ internal static class Program
_lastCompositeP = true;
return false;
}
OnPrimeCandidateConfirmed();
}
searchedMersenne = true;
@@ -15,15 +15,9 @@ public class GpuContextPoolTests
{
GpuContextPool.DisposeAll();
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try
{
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
}
finally
{
lease.Dispose();
}
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
GpuContextPool.DisposeAll();
}
@@ -36,27 +30,17 @@ public class GpuContextPoolTests
Context firstContext;
Accelerator firstAccelerator;
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
{
firstContext = lease.Context;
firstAccelerator = lease.Accelerator;
}
finally
{
lease.Dispose();
}
lease = GpuContextPool.RentPreferred(preferCpu: true);
try
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
{
lease.Context.Should().BeSameAs(firstContext);
lease.Accelerator.Should().BeSameAs(firstAccelerator);
}
finally
{
lease.Dispose();
}
GpuContextPool.DisposeAll();
}
@@ -68,27 +52,17 @@ public class GpuContextPoolTests
GpuContextPool.DisposeAll();
Context firstContext;
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
{
firstContext = lease.Context;
}
finally
{
lease.Dispose();
}
GpuContextPool.DisposeAll();
lease = GpuContextPool.RentPreferred(preferCpu: true);
try
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
{
lease.Context.Should().NotBeSameAs(firstContext);
}
finally
{
lease.Dispose();
}
GpuContextPool.DisposeAll();
}
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using FluentAssertions;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
using Xunit;
@@ -196,17 +199,10 @@ public class GpuKernelCompilationTests
[MemberData(nameof(KernelLoaders))]
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
{
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try
{
var accelerator = lease.Accelerator;
Action action = () => compile(accelerator);
action.Should().NotThrow($"kernel {name} should compile");
}
finally
{
lease.Dispose();
}
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
var accelerator = lease.Accelerator;
Action action = () => compile(accelerator);
action.Should().NotThrow($"kernel {name} should compile");
}
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
@@ -150,36 +150,29 @@ public class NttGpuMathTests
var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL);
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
try
using var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator;
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull();
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
var cacheType = cache.GetType();
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
int count = 7; // length 8 -> 7 twiddles
var forward = new GpuUInt128[count];
var inverse = new GpuUInt128[count];
twiddlesMont.CopyToCPU(forward);
twiddlesInvMont.CopyToCPU(inverse);
for (int i = 0; i < count; i++)
{
var accelerator = gpu.Accelerator;
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull();
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
var cacheType = cache.GetType();
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
int count = 7; // length 8 -> 7 twiddles
var forward = new GpuUInt128[count];
var inverse = new GpuUInt128[count];
twiddlesMont.CopyToCPU(forward);
twiddlesInvMont.CopyToCPU(inverse);
for (int i = 0; i < count; i++)
{
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
prod.Should().Be(r2);
}
}
finally
{
gpu.Dispose();
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
prod.Should().Be(r2);
}
}
@@ -345,23 +338,12 @@ public class NttGpuMathTests
values[1] = new GpuUInt128(0UL, 2UL);
var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL);
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
try
using (var gpu = GpuContextPool.Rent())
{
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
try
{
buffer.View.CopyFromCPU(ref values[0], values.Length);
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
}
finally
{
buffer.Dispose();
}
}
finally
{
gpu.Dispose();
buffer.View.CopyFromCPU(ref values[0], values.Length);
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
buffer.Dispose();
}
// Verify that the square cache now holds entries for the accelerator.
@@ -33,23 +33,16 @@ public class NttMontgomeryTests
NttGpuMath.Square(cpu, modulus, primitiveRoot);
// Device execution
GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
try
{
var accelerator = context.Accelerator;
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
using var context = GpuContextPool.Rent();
var accelerator = context.Accelerator;
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
for (int i = 0; i < cpu.Length; i++)
{
gpu[i].Should().Be(cpu[i]);
}
}
finally
for (int i = 0; i < cpu.Length; i++)
{
context.Dispose();
gpu[i].Should().Be(cpu[i]);
}
}
@@ -1,6 +1,8 @@
using System;
using System.Numerics;
using System.Reflection;
using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
using Xunit;
@@ -357,9 +359,10 @@ public class PrimeOrderGpuHeuristicsTests
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
result.Should().Be(3UL);
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
result.Order.Should().Be(3UL);
}
finally
{
@@ -11,21 +11,14 @@ public class GpuKernelPoolTests
[Trait("Category", "Fast")]
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
{
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try
{
var accelerator = lease.Accelerator;
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
var accelerator = lease.Accelerator;
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
second.Equals(first).Should().BeTrue();
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
}
finally
{
lease.Dispose();
}
second.Equals(first).Should().BeTrue();
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
}
[Fact]
@@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using FluentAssertions;
using PerfectNumbers.Core;
using Xunit;
namespace PerfectNumbers.Core.Tests;
@@ -67,7 +70,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
return new DummySession();
}
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{
for (int i = 0; i < primes.Length; i++)
{
@@ -77,7 +80,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
{
throw new NotSupportedException();
}
@@ -1,3 +1,4 @@
using System;
using FluentAssertions;
using PerfectNumbers.Core.Cpu;
using Xunit;
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu;
using PerfectNumbers.Core.Gpu;
using Xunit;
@@ -1,3 +1,5 @@
using System.Collections.Generic;
using System.Numerics;
using FluentAssertions;
using Xunit;
@@ -43,14 +45,14 @@ public class PrimeOrderCalculatorTests
{
ulong expected = ComputeOrderByDoubling(prime);
ulong result = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
MontgomeryDivisorData.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
result.Should().Be(expected);
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
result.Order.Should().Be(expected);
}
[Theory]
@@ -60,21 +62,21 @@ public class PrimeOrderCalculatorTests
{
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
ulong heuristic = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
prime,
previousOrder,
MontgomeryDivisorData.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
ulong strict = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
MontgomeryDivisorData.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault);
heuristic.Should().Be(strict);
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
heuristic.Order.Should().Be(strict.Order);
}
[Theory]
@@ -83,14 +85,14 @@ public class PrimeOrderCalculatorTests
[InlineData(3UL, 2UL)]
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
{
ulong result = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
MontgomeryDivisorData.FromModulus(prime),
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
result.Should().Be(expectedOrder);
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
result.Order.Should().Be(expectedOrder);
}
[Fact]
@@ -103,14 +105,14 @@ public class PrimeOrderCalculatorTests
maxPowChecks: 8,
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
ulong result = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
prime: 13UL,
previousOrder: null,
MontgomeryDivisorData.FromModulus(13UL),
config,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
config);
result.Should().Be(ComputeOrderByDoubling(13UL));
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
result.Order.Should().Be(ComputeOrderByDoubling(13UL));
}
[Fact]
@@ -124,14 +126,14 @@ public class PrimeOrderCalculatorTests
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
ulong heuristic = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
prime: 239UL,
previousOrder: null,
divisorData,
heuristicConfig,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
heuristicConfig);
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
heuristic.Order.Should().Be(ComputeOrderByDoubling(239UL));
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
smallFactorLimit: 2,
@@ -139,14 +141,14 @@ public class PrimeOrderCalculatorTests
maxPowChecks: 1,
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
ulong strict = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
prime: 239UL,
previousOrder: null,
divisorData,
strictConfig,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
strictConfig);
strict.Should().Be(heuristic);
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
strict.Order.Should().Be(heuristic.Order);
}
[Fact]
@@ -155,13 +157,13 @@ public class PrimeOrderCalculatorTests
{
UInt128 prime = UInt128.Parse("18446744073709641691");
UInt128 result = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
PrimeOrderCalculator.PrimeOrderResultWide result = PrimeOrderCalculator.Calculate(
prime,
previousOrder: null,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
result.Should().Be((UInt128)1229782938247309446UL);
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
result.Order.Should().Be((UInt128)1229782938247309446UL);
}
[Fact]
@@ -1,5 +1,6 @@
using System.Numerics;
using FluentAssertions;
using PerfectNumbers.Core;
using Xunit;
namespace PerfectNumbers.Core.Tests;
@@ -1,30 +0,0 @@
namespace PerfectNumbers.Core.Cpu;
internal struct FactorCacheLease
{
private Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? _cache;
public void EnsureInitialized(ref Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache)
{
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? current = cache;
if (current is null)
{
current = ThreadStaticPools.RentMersenneFactorCacheDictionary();
cache = current;
}
_cache = current;
}
public void Dispose()
{
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache = _cache;
if (cache is null)
{
return;
}
ThreadStaticPools.ReturnMersenneFactorCacheDictionary(cache);
_cache = null;
}
}
@@ -1,82 +0,0 @@
namespace PerfectNumbers.Core.Cpu;
internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{
private bool _disposed;
public void Reset()
{
_disposed = false;
}
public void CheckDivisor(
ulong divisor,
in MontgomeryDivisorData divisorData,
ulong divisorCycle,
in ReadOnlySpan<ulong> primes,
Span<byte> hits)
{
int length = primes.Length;
// EvenPerfectBitScanner always provides at least one exponent on this path.
// Keep the guard commented out for tests that might reuse the session differently.
// if (length == 0)
// {
// return;
// }
MontgomeryDivisorData cachedData = divisorData;
if (cachedData.Modulus != divisor)
{
cachedData = MontgomeryDivisorDataCache.Get(divisor);
}
if (divisorCycle == 0UL)
{
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
if (divisorCycle == 0UL)
{
hits.Clear();
return;
}
}
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
var exponentStepper = new ExponentRemainderStepper(cachedData);
if (!exponentStepper.IsValidModulus)
{
hits.Clear();
return;
}
var cycleStepper = new CycleRemainderStepper(divisorCycle);
ulong remainder = cycleStepper.Initialize(primes[0]);
hits[0] = remainder == 0UL
? (exponentStepper.ComputeNextIsUnity(primes[0]) ? (byte)1 : (byte)0)
: (byte)0;
for (int i = 1; i < length; i++)
{
remainder = cycleStepper.ComputeNext(primes[i]);
if (remainder != 0UL)
{
hits[i] = 0;
continue;
}
hits[i] = exponentStepper.ComputeNextIsUnity(primes[i]) ? (byte)1 : (byte)0;
}
}
public void Dispose()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(MersenneCpuDivisorScanSession), "Divisor scan session disposed twice.");
}
_disposed = true;
ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
}
}
@@ -1,4 +1,8 @@
using System.Runtime.CompilerServices;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Numerics;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core.Cpu;
@@ -6,19 +10,18 @@ namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
{
private readonly object _sync = new();
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
private readonly bool _useGpuPow2Mod;
private readonly bool _useGpuCycleComputation;
private ulong _divisorLimit;
private ulong _lastStatusDivisor;
private bool _isConfigured;
private int _batchSize = 1_024;
[ThreadStatic]
private static GpuUInt128WorkSet _divisorScanGpuWorkSet;
private struct GpuUInt128WorkSet
public MersenneNumberDivisorByDivisorCpuTester(bool useGpuPow2Mod = false, bool useGpuCycleComputation = false)
{
public GpuUInt128 Step;
public GpuUInt128 Divisor;
public GpuUInt128 Limit;
_useGpuPow2Mod = useGpuPow2Mod;
_useGpuCycleComputation = useGpuCycleComputation;
}
public int BatchSize
@@ -29,40 +32,70 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public void ConfigureFromMaxPrime(ulong maxPrime)
{
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
_lastStatusDivisor = 0UL;
lock (_sync)
{
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
_lastStatusDivisor = 0UL;
_isConfigured = true;
}
}
public ulong DivisorLimit
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _divisorLimit;
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
return _divisorLimit;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
public ulong GetAllowedMaxDivisor(ulong prime)
{
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
}
}
public bool IsPrime(ulong prime, out bool divisorsExhausted)
{
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
ulong allowedMax;
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
}
if (allowedMax < 3UL)
{
// EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
// trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
divisorsExhausted = true;
return true;
}
ulong processedCount;
ulong lastProcessed;
bool processedAll;
bool composite = CheckDivisors(
prime,
allowedMax,
out lastProcessed,
out processedAll,
out processedCount);
@@ -84,11 +117,26 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return true;
}
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{
ulong divisorLimit = _divisorLimit;
int length = primes.Length;
for (int index = 0; index < length; index++)
if (allowedMaxValues.Length < primes.Length)
{
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
}
ulong divisorLimit;
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
divisorLimit = _divisorLimit;
}
for (int index = 0; index < primes.Length; index++)
{
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
}
@@ -96,89 +144,87 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
{
MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession();
if (session is not null)
lock (_sync)
{
return session;
}
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
return new MersenneCpuDivisorScanSession();
if (_sessionPool.TryTake(out DivisorScanSession? session))
{
session.Reset();
return session;
}
return new DivisorScanSession(this);
}
}
private void ReturnSession(DivisorScanSession session)
{
_sessionPool.Add(session);
}
private bool CheckDivisors(
ulong prime,
ulong allowedMax,
out ulong lastProcessed,
out bool processedAll,
out ulong processedCount)
{
lastProcessed = 0UL;
processedCount = 0UL;
processedAll = false;
// 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)
// {
// return false;
// }
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
ref GpuUInt128 step = ref workSet.Step;
step.High = 0UL;
step.Low = prime;
step.ShiftLeft(1);
ref GpuUInt128 limit = ref workSet.Limit;
limit.High = 0UL;
limit.Low = allowedMax;
ref GpuUInt128 divisor = ref workSet.Divisor;
divisor.High = step.High;
divisor.Low = step.Low;
divisor.Add(1UL);
if (divisor.CompareTo(limit) > 0)
if (allowedMax < 3UL)
{
processedAll = true;
divisor.High = 0UL;
divisor.Low = 0UL;
step.High = 0UL;
step.Low = 0UL;
limit.High = 0UL;
limit.Low = 0UL;
return false;
}
FactorCacheLease factorCacheLease = new FactorCacheLease();
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
UInt128 step = (UInt128)prime << 1;
if (step == UInt128.Zero)
{
processedAll = true;
return false;
}
UInt128 limit = allowedMax;
UInt128 divisor = step + UInt128.One;
if (divisor > limit)
{
processedAll = true;
return false;
}
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
Dictionary<ulong, ulong>? cycleRemainderCache = null;
CycleHint cycleHint = default;
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
ulong stepHigh = step.High;
ulong stepLow = step.Low;
byte step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL);
byte step8 = (byte)(stepLow % 8UL);
byte step5 = (byte)(((stepHigh % 5UL) + (stepLow % 5UL)) % 5UL);
byte step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL);
byte step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL);
byte step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL);
byte step10 = (byte)(step % 10UL);
byte step8 = (byte)(step % 8UL);
byte step5 = (byte)(step % 5UL);
byte step3 = (byte)(step % 3UL);
byte step7 = (byte)(step % 7UL);
byte step11 = (byte)(step % 11UL);
ulong divisorHigh = divisor.High;
ulong divisorLow = divisor.Low;
byte remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL);
byte remainder8 = (byte)(divisorLow % 8UL);
byte remainder5 = (byte)(((divisorHigh % 5UL) + (divisorLow % 5UL)) % 5UL);
byte remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL);
byte remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL);
byte remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL);
byte remainder10 = (byte)(divisor % 10UL);
byte remainder8 = (byte)(divisor % 8UL);
byte remainder5 = (byte)(divisor % 5UL);
byte remainder3 = (byte)(divisor % 3UL);
byte remainder7 = (byte)(divisor % 7UL);
byte remainder11 = (byte)(divisor % 11UL);
// Keep the divisibility filters aligned with the divisor-cycle generator so the
// CPU path never requests cycles that were skipped during cache creation.
bool lastIsSeven = (prime & 3UL) == 3UL;
while (divisor.CompareTo(limit) <= 0)
while (divisor <= limit)
{
ulong candidate = divisor.Low;
ulong candidate = (ulong)divisor;
processedCount++;
lastProcessed = candidate;
bool admissible = lastIsSeven
? (remainder10 == 3 || remainder10 == 7 || remainder10 == 9)
@@ -187,56 +233,17 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
ulong divisorCycle;
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
if (TryResolveDivisorCycle(candidate, prime, divisorData, cycleCache, ref factorCache, ref cycleHint, out ulong divisorCycle) && divisorCycle != 0UL)
{
divisorCycle = cycleCache.GetCycleLength(candidate);
}
else
{
if (factorCache is null)
if (CheckDivisor(prime, candidate, divisorCycle, divisorData, ref cycleRemainderCache, ref cycleHint) != 0)
{
factorCacheLease.EnsureInitialized(ref factorCache);
processedAll = true;
return true;
}
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(
candidate,
prime,
divisorData,
factorCache,
out ulong computedCycle) || computedCycle == 0UL)
{
divisorCycle = cycleCache.GetCycleLength(candidate);
}
else
{
divisorCycle = computedCycle;
}
}
if (divisorCycle == prime)
{
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
factorCacheLease.Dispose();
factorCache = null;
divisor.High = 0UL;
divisor.Low = 0UL;
step.High = 0UL;
step.Low = 0UL;
limit.High = 0UL;
limit.Low = 0UL;
processedAll = true;
return true;
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
}
divisor.Add(step);
divisor += step;
remainder10 = AddMod(remainder10, step10, (byte)10);
remainder8 = AddMod(remainder8, step8, (byte)8);
remainder5 = AddMod(remainder5, step5, (byte)5);
@@ -245,25 +252,156 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
remainder11 = AddMod(remainder11, step11, (byte)11);
}
factorCacheLease.Dispose();
factorCache = null; // The pooled dictionary was returned to the thread-static slot.
processedAll = divisor.CompareTo(limit) > 0;
divisor.High = 0UL;
divisor.Low = 0UL;
step.High = 0UL;
step.Low = 0UL;
limit.High = 0UL;
limit.Low = 0UL;
processedAll = divisor > limit;
return false;
}
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
private byte CheckDivisor(
ulong prime,
ulong divisor,
ulong divisorCycle,
in MontgomeryDivisorData divisorData,
ref Dictionary<ulong, ulong>? cycleRemainders,
ref CycleHint cycleHint)
{
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
if (divisorCycle == 0UL)
{
throw new InvalidOperationException($"Cycle length resolution returned zero for divisor {divisor}.");
}
Dictionary<ulong, ulong>? remainderCache = cycleRemainders;
if (_useGpuPow2Mod && remainderCache is null)
{
remainderCache = new Dictionary<ulong, ulong>(8);
cycleRemainders = remainderCache;
}
ulong reducedExponent;
if (remainderCache is not null && remainderCache.TryGetValue(divisorCycle, out ulong cachedRemainder))
{
reducedExponent = cachedRemainder;
}
else if (cycleHint.HasCycle && cycleHint.LastCycleLength == divisorCycle)
{
reducedExponent = cycleHint.LastReducedExponent;
}
else
{
reducedExponent = prime % divisorCycle;
if (remainderCache is not null)
{
remainderCache[divisorCycle] = reducedExponent;
}
}
cycleHint.LastCycleLength = divisorCycle;
cycleHint.LastReducedExponent = reducedExponent;
cycleHint.HasCycle = true;
if (_useGpuPow2Mod && remainderCache is not null)
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(reducedExponent, divisor, out ulong remainder, divisorData);
if (status == GpuPow2ModStatus.Success)
{
return remainder == 1UL ? (byte)1 : (byte)0;
}
PrimeOrderGpuHeuristics.ReportPow2Failure(
"by-divisor residue scan",
divisor,
status);
}
ulong residue = reducedExponent.Pow2MontgomeryModFromCycleRemainder(divisorData);
return residue == 1UL ? (byte)1 : (byte)0;
}
private bool TryResolveDivisorCycle(
ulong divisor,
ulong prime,
in MontgomeryDivisorData divisorData,
DivisorCycleCache cycleCache,
ref Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache,
ref CycleHint cycleHint,
out ulong cycleLength)
{
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
{
cycleLength = cycleCache.GetCycleLength(divisor);
if (cycleLength == 0UL)
{
throw new InvalidOperationException($"Missing cached cycle length for divisor {divisor}.");
}
cycleHint.LastCycleLength = cycleLength;
cycleHint.HasCycle = true;
return true;
}
if (_useGpuCycleComputation && TryCalculateCycleLengthGpu(divisor, prime, divisorData, ref cycleHint, out cycleLength))
{
return cycleLength != 0UL;
}
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
if (MersenneDivisorCycles.TryCalculateCycleLengthForExponent(
divisor,
prime,
divisorData,
factorCache,
out cycleLength,
allowGpuPow2: _useGpuPow2Mod) && cycleLength != 0UL)
{
cycleHint.LastCycleLength = cycleLength;
cycleHint.HasCycle = true;
return true;
}
cycleLength = cycleCache.GetCycleLength(divisor);
if (cycleLength == 0UL)
{
return false;
}
cycleHint.LastCycleLength = cycleLength;
cycleHint.HasCycle = true;
return true;
}
private bool TryCalculateCycleLengthGpu(
ulong divisor,
ulong prime,
in MontgomeryDivisorData divisorData,
ref CycleHint cycleHint,
out ulong cycleLength)
{
PrimeOrderCalculator.PrimeOrderSearchConfig config = PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault;
ulong? previousOrder = cycleHint.HasCycle ? cycleHint.LastCycleLength : null;
try
{
if (!PrimeOrderGpuHeuristics.TryCalculateOrder(divisor, previousOrder, config, divisorData, out PrimeOrderCalculator.PrimeOrderResult result))
{
throw new InvalidOperationException("GPU requested fallback.");
}
cycleLength = result.Order;
if (cycleLength == 0UL)
{
return false;
}
cycleHint.LastCycleLength = cycleLength;
cycleHint.HasCycle = true;
return true;
}
catch (InvalidOperationException ex)
{
Console.Error.WriteLine($"GPU cycle length computation failed for divisor {divisor}: {ex.Message}");
throw;
}
}
private static byte AddMod(byte value, byte delta, byte modulus)
{
int sum = value + delta;
@@ -310,6 +448,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return (1UL << (int)(maxPrime - 1UL)) - 1UL;
}
private struct CycleHint
{
public ulong LastCycleLength;
public ulong LastReducedExponent;
public bool HasCycle;
}
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
{
if (prime <= 1UL)
@@ -325,5 +470,90 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
}
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
private bool _disposed;
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
{
_owner = owner;
}
internal void Reset()
{
_disposed = false;
}
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DivisorScanSession));
}
int length = primes.Length;
if (length == 0)
{
return;
}
MontgomeryDivisorData cachedData = divisorData;
if (cachedData.Modulus != divisor)
{
cachedData = MontgomeryDivisorDataCache.Get(divisor);
}
if (divisorCycle == 0UL)
{
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
if (divisorCycle == 0UL)
{
hits.Clear();
return;
}
}
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
var exponentStepper = new ExponentRemainderStepper(cachedData);
if (!exponentStepper.IsValidModulus)
{
hits.Clear();
return;
}
var cycleStepper = new CycleRemainderStepper(divisorCycle);
ulong remainder = cycleStepper.Initialize(primes[0]);
hits[0] = remainder == 0UL
? (exponentStepper.ComputeNextIsUnity(primes[0]) ? (byte)1 : (byte)0)
: (byte)0;
for (int i = 1; i < length; i++)
{
remainder = cycleStepper.ComputeNext(primes[i]);
if (remainder != 0UL)
{
hits[i] = 0;
continue;
}
hits[i] = exponentStepper.ComputeNextIsUnity(primes[i]) ? (byte)1 : (byte)0;
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_owner.ReturnSession(this);
}
}
}
+12 -12
View File
@@ -1,8 +1,9 @@
using System.Runtime.CompilerServices;
using System;
using System.Numerics;
namespace PerfectNumbers.Core;
public struct CycleRemainderStepper
internal struct CycleRemainderStepper
{
private readonly ulong _cycleLength;
private ulong _previousPrime;
@@ -22,16 +23,15 @@ public struct CycleRemainderStepper
_hasState = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
// TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
// measured fastest in MersenneDivisorCycleLengthGpuBenchmarks, avoiding the extra
// method call when scanners need to restart stepping.
_previousPrime = 0UL;
_currentRemainder = 0UL;
_hasState = false;
}
public void Reset()
{
// TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
// measured fastest in MersenneDivisorCycleLengthGpuBenchmarks, avoiding the extra
// method call when scanners need to restart stepping.
_previousPrime = 0UL;
_currentRemainder = 0UL;
_hasState = false;
}
public ulong Initialize(ulong prime)
{
@@ -1,46 +0,0 @@
namespace PerfectNumbers.Core;
internal static class DeterministicRandom
{
[ThreadStatic]
private static ulong s_threadState;
private static long s_seedCounter;
public static ulong NextUInt64()
{
ulong state = s_threadState;
if (state == 0UL)
{
state = InitializeThreadState();
}
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
s_threadState = state;
return state * 2685821657736338717UL;
}
public static UInt128 NextUInt128()
{
UInt128 high = (UInt128)NextUInt64();
UInt128 low = (UInt128)NextUInt64();
return (high << 64) | low;
}
private static ulong InitializeThreadState()
{
ulong seed = (ulong)Interlocked.Increment(ref s_seedCounter);
seed += 0x9E3779B97F4A7C15UL;
seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL;
seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL;
seed ^= seed >> 31;
if (seed == 0UL)
{
seed = 0x9E3779B97F4A7C15UL;
}
return seed;
}
}
+3
View File
@@ -1,7 +1,10 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu;
@@ -47,7 +47,7 @@ internal struct ExponentRemainderStepper
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
// 2× gains for large exponents.
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
_previousExponent = exponent;
_hasState = true;
return ReduceCurrent();
@@ -59,7 +59,7 @@ internal struct ExponentRemainderStepper
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
ulong multiplier = delta.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
_previousExponent = exponent;
return ReduceCurrent();
@@ -77,7 +77,7 @@ internal struct ExponentRemainderStepper
{
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
// residue checks align with the optimized ProcessEightBitWindows timings.
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
_previousExponent = exponent;
_hasState = true;
return _currentMontgomery == _montgomeryOne;
@@ -88,7 +88,7 @@ internal struct ExponentRemainderStepper
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
// TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
// currently lags behind the benchmarked implementation for huge divisor cycles.
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
ulong multiplier = delta.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
_previousExponent = exponent;
return _currentMontgomery == _montgomeryOne;
@@ -123,7 +123,7 @@ internal struct ExponentRemainderStepper
{
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
_previousExponent = exponent;
_hasState = true;
isUnity = _currentMontgomery == _montgomeryOne;
-8
View File
@@ -1,8 +0,0 @@
namespace PerfectNumbers.Core
{
public readonly struct FactorEntry(ulong value, int exponent)
{
public readonly ulong Value = value;
public readonly int Exponent = exponent;
}
}
-8
View File
@@ -1,8 +0,0 @@
namespace PerfectNumbers.Core;
public readonly struct FactorEntry128(in UInt128 value, int exponent)
{
public readonly UInt128 Value = value;
public readonly int Exponent = exponent;
}
+1 -1
View File
@@ -126,7 +126,7 @@ public static class GpuContextPool
}
}
public struct GpuContextLease
public struct GpuContextLease : IDisposable
{
private readonly PooledContext _ctx;
private bool disposed;
+1 -1
View File
@@ -677,7 +677,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
// GC.SuppressFinalize(this);
}
public readonly struct ExecutionScope
public readonly struct ExecutionScope : IDisposable
{
private readonly object? _lock;
+2
View File
@@ -1,3 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core.Gpu;
+13 -12
View File
@@ -1,3 +1,4 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using ILGPU.Algorithms;
@@ -266,7 +267,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low);
public void Add(in GpuUInt128 other) => AddInternal(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddInternal(ulong otherHigh, ulong otherLow)
@@ -293,17 +294,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
public void AddMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
public void AddMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
{
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
AddModInternal(value.High, value.Low, in readOnlyMod);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddMod(GpuUInt128 value, GpuUInt128 modulus)
public void AddMod(in GpuUInt128 value, in GpuUInt128 modulus)
{
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
AddModInternal(value.High, value.Low, in readOnlyMod);
@@ -389,7 +390,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddMod(GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
public void AddMod(in GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddMod(ulong value, ulong modulus)
@@ -413,7 +414,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Sub(GpuUInt128 other) => SubInternal(other.High, other.Low);
public void Sub(in GpuUInt128 other) => SubInternal(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SubInternal(ulong otherHigh, ulong otherLow)
@@ -460,7 +461,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Mul64(GpuUInt128 other) => Mul64Internal(other.High, other.Low);
public void Mul64(in GpuUInt128 other) => Mul64Internal(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Mul64Internal(ulong otherHigh, ulong otherLow)
@@ -587,7 +588,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int TrailingZeroCount(GpuUInt128 value)
public static int TrailingZeroCount(in GpuUInt128 value)
{
if (value.Low != 0UL)
{
@@ -645,17 +646,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SubMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
public void SubMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SubMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
public void SubMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
{
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
SubModInternal(value.High, value.Low, in readOnlyMod);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SubMod(GpuUInt128 value, GpuUInt128 modulus)
public void SubMod(in GpuUInt128 value, in GpuUInt128 modulus)
{
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
SubModInternal(value.High, value.Low, in readOnlyMod);
@@ -726,7 +727,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Mul(GpuUInt128 other)
public void Mul(in GpuUInt128 other)
{
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
@@ -1,9 +1,14 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Numerics;
using ILGPU;
using ILGPU.Runtime;
using MontgomeryDivisorData = PerfectNumbers.Core.MontgomeryDivisorData;
using MontgomeryDivisorDataCache = PerfectNumbers.Core.MontgomeryDivisorDataCache;
using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
namespace PerfectNumbers.Core.Gpu;
@@ -143,7 +148,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
return true;
}
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{
if (allowedMaxValues.Length < primes.Length)
{
@@ -546,7 +551,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
}
ulong exponent = exponents[index];
hits[index] = ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
hits[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
}
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
@@ -578,7 +583,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
_disposed = false;
}
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
{
if (_disposed)
{
@@ -765,7 +770,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong exponent = exponents[index];
results[index] = ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisor, keepMontgomery: true);
results[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: true);
}
public ulong DivisorLimit
@@ -1,5 +1,8 @@
using System;
using System.Buffers;
using System.Threading;
using System.Runtime.InteropServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
@@ -1,5 +1,6 @@
using System.Buffers;
using System.Runtime.InteropServices;
using System.Threading;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
@@ -1,11 +1,15 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
using System.Numerics;
using System.Globalization;
using PerfectNumbers.Core;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using ILGPU.Runtime.OpenCL;
namespace PerfectNumbers.Core.Gpu;
internal enum GpuPow2ModStatus
@@ -25,6 +29,25 @@ internal static partial class PrimeOrderGpuHeuristics
private static readonly ConcurrentDictionary<Accelerator, OrderKernelLauncher> OrderKernelCache = new();
private static readonly ConcurrentDictionary<Accelerator, SmallPrimeDeviceCache> SmallPrimeDeviceCaches = new();
private static void ReportPow2FailureCore(string context, string modulusDescription, GpuPow2ModStatus status)
{
string message = $"GPU pow2mod failed with status {status} for divisor {modulusDescription} during {context}.";
Console.Error.WriteLine(message);
throw new InvalidOperationException(message);
}
internal static void ReportPow2Failure(string context, ulong modulus, GpuPow2ModStatus status)
{
string modulusDescription = modulus.ToString(CultureInfo.InvariantCulture);
ReportPow2FailureCore(context, modulusDescription, status);
}
internal static void ReportPow2Failure(string context, UInt128 modulus, GpuPow2ModStatus status)
{
string modulusDescription = modulus.ToString(CultureInfo.InvariantCulture);
ReportPow2FailureCore(context, modulusDescription, status);
}
public readonly struct OrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode)
{
public readonly ulong PreviousOrder = previousOrder;
@@ -413,9 +436,9 @@ internal static partial class PrimeOrderGpuHeuristics
ulong? previousOrder,
PrimeOrderCalculator.PrimeOrderSearchConfig config,
in MontgomeryDivisorData divisorData,
out ulong order)
out PrimeOrderCalculator.PrimeOrderResult result)
{
order = 0UL;
result = default;
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = lease.EnterExecutionScope();
@@ -495,17 +518,18 @@ internal static partial class PrimeOrderGpuHeuristics
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
}
ulong order = 0UL;
resultBuffer.View.CopyToCPU(ref order, 1);
if (kernelStatus == PrimeOrderKernelStatus.FactoringFailure)
{
order = 0UL;
}
PrimeOrderCalculator.PrimeOrderStatus finalStatus = kernelStatus == PrimeOrderKernelStatus.Found
? PrimeOrderCalculator.PrimeOrderStatus.Found
: PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved;
result = new PrimeOrderCalculator.PrimeOrderResult(finalStatus, order);
DisposeResources();
lease.Dispose();
return order != 0UL;
return true;
void DisposeResources()
{
@@ -809,7 +833,7 @@ internal static partial class PrimeOrderGpuHeuristics
}
ulong reduced = phi / factor;
if (ULongExtensions.Pow2MontgomeryModWindowedKernel(reduced, divisor, keepMontgomery: false) == 1UL)
if (reduced.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL)
{
return false;
}
@@ -1593,7 +1617,7 @@ internal static partial class PrimeOrderGpuHeuristics
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
{
return ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisor, keepMontgomery: false) == 1UL;
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL;
}
private static ulong CalculateByDoublingKernel(ulong prime)
@@ -1793,7 +1817,7 @@ internal static partial class PrimeOrderGpuHeuristics
return 0UL;
}
return ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisorData, keepMontgomery: false);
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false);
}
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
@@ -1809,7 +1833,7 @@ internal static partial class PrimeOrderGpuHeuristics
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
{
ulong exponent = exponents[index];
remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisor, keepMontgomery: false);
remainders[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false);
}
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
@@ -1,3 +1,4 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -1,3 +1,5 @@
using System;
namespace PerfectNumbers.Core;
public interface IMersenneNumberDivisorByDivisorTester
@@ -14,10 +16,10 @@ public interface IMersenneNumberDivisorByDivisorTester
IDivisorScanSession CreateDivisorSession();
void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
public interface IDivisorScanSession : IDisposable
{
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits);
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits);
}
}
+89 -174
View File
@@ -1,9 +1,11 @@
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using PerfectNumbers.Core.Gpu;
using ILGPU;
using ILGPU.Runtime;
@@ -15,6 +17,7 @@ public class MersenneDivisorCycles
private List<(ulong divisor, ulong cycleLength)> _table = [];
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
private ulong[]? _smallCycles;
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
public MersenneDivisorCycles()
@@ -99,7 +102,7 @@ public class MersenneDivisorCycles
return cycleLength == exponent;
}
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
}
public static bool CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
@@ -123,7 +126,7 @@ public class MersenneDivisorCycles
}
}
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
}
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
@@ -231,14 +234,14 @@ public class MersenneDivisorCycles
return (UInt128)cycle;
}
UInt128 wideOrder = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResultWide wideResult = PrimeOrderCalculator.Calculate(
divisor,
previousOrder: null,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
if (wideOrder != UInt128.Zero)
if (wideResult.Order != UInt128.Zero)
{
return wideOrder;
return wideResult.Order;
}
// Otherwise, find order of 2 mod divisor
@@ -266,7 +269,8 @@ public class MersenneDivisorCycles
ulong exponent,
in MontgomeryDivisorData divisorData,
Dictionary<ulong, FactorCacheEntry>? factorCache,
out ulong cycleLength)
out ulong cycleLength,
bool allowGpuPow2 = false)
{
cycleLength = 0UL;
@@ -282,25 +286,24 @@ public class MersenneDivisorCycles
return true;
}
ulong computedOrder = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
divisor,
previousOrder: null,
divisorData,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
if (computedOrder != 0UL)
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu,
allowGpuPow2);
if (orderResult.Order != 0UL)
{
cycleLength = computedOrder;
cycleLength = orderResult.Order;
return true;
}
ulong phi = divisor - 1UL;
// A zero totient would imply divisor == 0, which never occurs on the tested path. Keep the guard documented without
// branching in the hot loop.
// if (phi == 0UL)
// {
// return false;
// }
if (phi == 0UL)
{
return false;
}
int twoCount = BitOperations.TrailingZeroCount(phi);
ulong reducedPhi = phi >> twoCount;
@@ -310,32 +313,24 @@ public class MersenneDivisorCycles
}
ulong k = reducedPhi / exponent;
// k == 0 would indicate reducedPhi < exponent, which the EvenPerfectBitScanner call flow prevents. Document the
// assumption instead of branching.
// if (k == 0UL)
// {
// return false;
// }
Dictionary<ulong, int> factorCounts = ThreadStaticPools.RentFactorCountDictionary();
bool success = false;
if (k == 0UL)
{
return false;
}
Dictionary<ulong, int> factorCounts = new(8);
if (twoCount > 0)
{
factorCounts[2UL] = twoCount;
}
bool exponentAccumulated = AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true);
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCache, factorCounts, cacheResult: false);
if (factorsAccumulated)
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
{
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
success = true;
return false;
}
ThreadStaticPools.ReturnFactorCountDictionary(factorCounts);
return success;
cycleLength = ReduceOrder(divisorData, phi, factorCounts, allowGpuPow2);
return true;
}
private static bool AccumulateFactors(
@@ -349,27 +344,18 @@ public class MersenneDivisorCycles
return true;
}
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry? factorization, out bool releaseAfterUse) || factorization is null)
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry factorization))
{
return false;
}
FactorCacheEntry actualFactorization = factorization;
KeyValuePair<ulong, int>[]? pairs = actualFactorization.Pairs;
if (pairs is not null)
{
int length = actualFactorization.Count;
ulong[] primes = factorization.Primes;
byte[] exponents = factorization.Exponents;
int length = factorization.Count;
for (int i = 0; i < length; i++)
{
KeyValuePair<ulong, int> pair = pairs[i];
AddFactor(counts, pair.Key, pair.Value);
}
}
if (releaseAfterUse)
for (int i = 0; i < length; i++)
{
actualFactorization.ReturnToPool();
AddFactor(counts, primes[i], exponents[i]);
}
return true;
@@ -379,57 +365,37 @@ public class MersenneDivisorCycles
ulong value,
Dictionary<ulong, FactorCacheEntry>? cache,
bool cacheResult,
out FactorCacheEntry? factorization,
out bool releaseAfterUse)
out FactorCacheEntry factorization)
{
releaseAfterUse = false;
if (cache is not null && cache.TryGetValue(value, out FactorCacheEntry? cachedEntry))
if (cache is not null && cache.TryGetValue(value, out factorization))
{
factorization = cachedEntry;
return true;
}
Dictionary<ulong, int> scratch = ThreadStaticPools.RentFactorScratchDictionary();
bool factored = TryFactorIntoCountsInternal(value, scratch);
if (!factored)
Dictionary<ulong, int> scratch = new(8);
if (!TryFactorIntoCountsInternal(value, scratch))
{
ThreadStaticPools.ReturnFactorScratchDictionary(scratch);
factorization = null;
factorization = default!;
return false;
}
int count = scratch.Count;
FactorCacheEntry entry = ThreadStaticPools.RentFactorCacheEntry();
if (count == 0)
ulong[] primes = new ulong[count];
byte[] exponents = new byte[count];
int index = 0;
foreach (KeyValuePair<ulong, int> entry in scratch)
{
entry.Initialize(null, 0, fromPool: false);
factorization = entry;
releaseAfterUse = cache is null || !cacheResult;
if (cache is not null && cacheResult)
{
cache[value] = entry;
}
ThreadStaticPools.ReturnFactorScratchDictionary(scratch);
return true;
primes[index] = entry.Key;
exponents[index] = checked((byte)entry.Value);
index++;
}
KeyValuePair<ulong, int>[] pairs = ThreadStaticPools.FactorKeyValuePairPool.Rent(count);
((ICollection<KeyValuePair<ulong, int>>)scratch).CopyTo(pairs, 0);
ThreadStaticPools.ReturnFactorScratchDictionary(scratch);
Array.Sort(pairs, 0, count, FactorPairValueComparer.Instance);
entry.Initialize(pairs, count, fromPool: true);
factorization = entry;
Array.Sort(primes, exponents);
factorization = new FactorCacheEntry(primes, exponents, count);
if (cache is not null && cacheResult)
{
cache[value] = entry;
}
else
{
releaseAfterUse = true;
cache[value] = factorization;
}
return true;
@@ -437,11 +403,10 @@ public class MersenneDivisorCycles
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
{
// The EvenPerfectBitScanner only requests factors for values >= 2 here. Leave the guard documented without executing it.
// if (value <= 1UL)
// {
// return true;
// }
if (value <= 1UL)
{
return true;
}
ulong remaining = value;
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
@@ -456,7 +421,6 @@ public class MersenneDivisorCycles
break;
}
// TODO: Reuse the remainder-tracking update from the cycle heuristic so repeated divisions disappear from this loop.
while ((remaining % prime) == 0UL)
{
AddFactor(counts, prime);
@@ -469,43 +433,20 @@ public class MersenneDivisorCycles
return true;
}
// TODO: Benchmark PrimeTester.IsPrimeInternal against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites.
if (PrimeTester.IsPrimeInternal(remaining, CancellationToken.None))
{
AddFactor(counts, remaining);
return true;
}
// TODO: Evaluate folding the remainder-tracking mechanism into PollardRho64 so the walk leverages previously computed residues.
ulong factor = PollardRho64(remaining);
if (factor == 0UL || factor == remaining)
{
// Pollard-Rho can still stall on rare stubborn composites during test and benchmark runs, so keep the failure guard in place
// and let the caller fall back to the cached cycle calculator.
return false;
}
// TODO: Investigate computing this quotient via remainder tracking or reciprocal multiplication to avoid the division.
ulong quotient = remaining / factor;
// Pollard-Rho never returns factors <= 1 on the EvenPerfectBitScanner path; keep the old guard documented
// for completeness without branching in the hot path.
// if (factor > 1UL && !TryFactorIntoCountsInternal(factor, counts))
// {
// return false;
// }
if (!TryFactorIntoCountsInternal(factor, counts))
{
return false;
}
// Guard the recursive calls to terminate once Pollard-Rho reduces either branch to 1.
if (quotient > 1UL && !TryFactorIntoCountsInternal(quotient, counts))
{
return false;
}
return true;
return TryFactorIntoCountsInternal(factor, counts) && TryFactorIntoCountsInternal(quotient, counts);
}
private static void AddFactor(Dictionary<ulong, int> counts, ulong factor)
@@ -532,7 +473,7 @@ public class MersenneDivisorCycles
}
}
private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts, bool allowGpuPow2)
{
if (factorCounts.Count == 0)
{
@@ -566,7 +507,7 @@ public class MersenneDivisorCycles
}
ulong candidate = order / prime;
if (candidate.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL)
if (Pow2EqualsOne(candidate, divisorData, allowGpuPow2))
{
order = candidate;
continue;
@@ -584,6 +525,26 @@ public class MersenneDivisorCycles
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool Pow2EqualsOne(ulong exponent, in MontgomeryDivisorData divisorData, bool allowGpuPow2)
{
if (allowGpuPow2)
{
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, divisorData.Modulus, out ulong remainder, divisorData);
if (status == GpuPow2ModStatus.Success)
{
return remainder == 1UL;
}
PrimeOrderGpuHeuristics.ReportPow2Failure(
"divisor cycle reduction",
divisorData.Modulus,
status);
}
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
}
private static ulong PollardRho64(ulong n)
{
if ((n & 1UL) == 0UL)
@@ -660,66 +621,19 @@ public class MersenneDivisorCycles
return BinaryPrimitives.ReadUInt64LittleEndian(buffer);
}
private sealed class FactorPairValueComparer : IComparer<KeyValuePair<ulong, int>>
public readonly struct FactorCacheEntry
{
public static readonly FactorPairValueComparer Instance = new FactorPairValueComparer();
private FactorPairValueComparer()
public FactorCacheEntry(ulong[] primes, byte[] exponents, int count)
{
}
public int Compare(KeyValuePair<ulong, int> x, KeyValuePair<ulong, int> y)
{
if (x.Key < y.Key)
{
return -1;
}
if (x.Key > y.Key)
{
return 1;
}
return 0;
}
}
public sealed class FactorCacheEntry
{
internal FactorCacheEntry? Next;
internal KeyValuePair<ulong, int>[]? Pairs;
internal int Count;
private bool _fromPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Reset()
{
Pairs = null;
Count = 0;
_fromPool = false;
Next = null;
}
internal void Initialize(KeyValuePair<ulong, int>[]? pairs, int count, bool fromPool)
{
Pairs = pairs;
Primes = primes;
Exponents = exponents;
Count = count;
_fromPool = fromPool;
Next = null;
}
public ulong[] Primes { get; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ReturnToPool()
{
KeyValuePair<ulong, int>[]? pairs = Pairs;
if (_fromPool && pairs is not null)
{
ThreadStaticPools.FactorKeyValuePairPool.Return(pairs, clearArray: false);
}
public byte[] Exponents { get; }
Reset();
ThreadStaticPools.ReturnFactorCacheEntry(this);
}
public int Count { get; }
}
public static (long nextPosition, long completeCount) FindLast(string path)
@@ -1032,7 +946,7 @@ public class MersenneDivisorCycles
return CalculateCycleLengthFallback(divisor);
}
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength)
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool allowGpuPow2 = false)
{
if ((divisor & (divisor - 1UL)) == 0UL)
{
@@ -1048,15 +962,16 @@ public class MersenneDivisorCycles
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
{
ulong computedOrder = PrimeOrderCalculator.Calculate(
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
divisor,
previousOrder: null,
divisorData,
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
if (computedOrder != 0UL)
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu,
allowGpuPow2);
if (orderResult.Order != 0UL)
{
cycleLength = computedOrder;
cycleLength = orderResult.Order;
return true;
}
}
@@ -1,262 +1,269 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Open.Numeric.Primes;
using System.Runtime.InteropServices;
namespace PerfectNumbers.Core;
public static class MersenneNumberDivisorByDivisorTester
{
public static void Run(
List<ulong> candidates,
IMersenneNumberDivisorByDivisorTester tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool> printResult,
int threadCount)
{
if (candidates.Count == 0)
{
Console.WriteLine("No candidates were provided for --mersenne=bydivisor.");
return;
}
public static void Run(
List<ulong> candidates,
IMersenneNumberDivisorByDivisorTester tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime,
Action markComposite,
Action clearComposite,
Action<ulong, bool, bool, bool> printResult,
int threadCount)
{
if (candidates.Count == 0)
{
Console.WriteLine("No candidates were provided for --mersenne=bydivisor.");
return;
}
candidates.Sort();
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
int candidateCount = candidateSpan.Length;
_ = threadCount;
bool applyStartPrime = startPrime > 0UL;
int skippedByPreviousResults = 0;
candidates.Sort();
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
int candidateCount = candidateSpan.Length;
ArrayPool<ulong> pool = ThreadStaticPools.UlongPool;
if (previousResults is not null && previousResults.Count > 0)
{
int recordedCount = previousResults.Count;
ulong[] recordedCandidates = pool.Rent(recordedCount);
bool applyStartPrime = startPrime > 0UL;
int skippedByPreviousResults = 0;
Span<ulong> recordedSpan = recordedCandidates.AsSpan(0, recordedCount);
int recordedIndex = 0;
foreach (ulong value in previousResults.Keys)
{
recordedSpan[recordedIndex++] = value;
}
if (previousResults is not null && previousResults.Count > 0)
{
int recordedCount = previousResults.Count;
ulong[] recordedCandidates = ArrayPool<ulong>.Shared.Rent(recordedCount);
try
{
Span<ulong> recordedSpan = recordedCandidates.AsSpan(0, recordedCount);
int recordedIndex = 0;
foreach (ulong value in previousResults.Keys)
{
recordedSpan[recordedIndex++] = value;
}
recordedSpan.Sort();
recordedSpan.Sort();
int writeIndex = 0;
int skipIndex = 0;
int writeIndex = 0;
int skipIndex = 0;
for (int readIndex = 0; readIndex < candidateCount; readIndex++)
{
ulong candidate = candidateSpan[readIndex];
for (int readIndex = 0; readIndex < candidateCount; readIndex++)
{
ulong candidate = candidateSpan[readIndex];
while (skipIndex < recordedCount && recordedSpan[skipIndex] < candidate)
{
skipIndex++;
}
while (skipIndex < recordedCount && recordedSpan[skipIndex] < candidate)
{
skipIndex++;
}
if (skipIndex < recordedCount && recordedSpan[skipIndex] == candidate)
{
skippedByPreviousResults++;
skipIndex++;
continue;
}
if (skipIndex < recordedCount && recordedSpan[skipIndex] == candidate)
{
skippedByPreviousResults++;
skipIndex++;
continue;
}
candidateSpan[writeIndex++] = candidate;
}
candidateSpan[writeIndex++] = candidate;
}
if (writeIndex < candidateCount)
{
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
candidateSpan = CollectionsMarshal.AsSpan(candidates);
candidateCount = candidateSpan.Length;
}
if (writeIndex < candidateCount)
{
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
candidateSpan = CollectionsMarshal.AsSpan(candidates);
candidateCount = candidateSpan.Length;
}
}
finally
{
ArrayPool<ulong>.Shared.Return(recordedCandidates, clearArray: true);
}
}
pool.Return(recordedCandidates, clearArray: true);
}
if (skippedByPreviousResults > 0)
{
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
}
if (skippedByPreviousResults > 0)
{
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
}
int startIndex = 0;
if (applyStartPrime)
{
startIndex = candidates.BinarySearch(startPrime);
if (startIndex < 0)
{
startIndex = ~startIndex;
}
else
{
while (startIndex > 0 && candidates[startIndex - 1] >= startPrime)
{
startIndex--;
}
}
}
int startIndex = 0;
if (applyStartPrime)
{
startIndex = candidates.BinarySearch(startPrime);
if (startIndex < 0)
{
startIndex = ~startIndex;
}
else
{
while (startIndex > 0 && candidates[startIndex - 1] >= startPrime)
{
startIndex--;
}
}
}
List<ulong> primesToTest = candidates;
Span<ulong> primesSpan = CollectionsMarshal.AsSpan(primesToTest);
ulong maxPrime = 0UL;
int primeWriteIndex = 0;
List<ulong> primesToTest = candidates;
Span<ulong> primesSpan = CollectionsMarshal.AsSpan(primesToTest);
ulong maxPrime = 0UL;
int primeWriteIndex = 0;
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
bool hasPrime = primeEnumerator.MoveNext();
ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
// This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
// We don't need to additionally check it.
for (int index = startIndex; index < candidateCount; index++)
{
ulong candidate = primesSpan[index];
// using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
// bool hasPrime = primeEnumerator.MoveNext();
// ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
if (candidate <= 1UL)
{
markComposite();
printResult(candidate, false, false, false);
continue;
}
for (int index = startIndex; index < candidateCount; index++)
{
ulong candidate = primesSpan[index];
while (hasPrime && currentPrime < candidate)
{
hasPrime = primeEnumerator.MoveNext();
if (!hasPrime)
{
break;
}
if (candidate <= 1UL)
{
markComposite();
printResult(candidate, false, false, false);
continue;
}
currentPrime = primeEnumerator.Current;
}
// This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
// We don't need to additionally check it.
if (!hasPrime || currentPrime != candidate)
{
markComposite();
printResult(candidate, false, false, false);
continue;
}
// while (hasPrime && currentPrime < candidate)
// {
// hasPrime = primeEnumerator.MoveNext();
// if (!hasPrime)
// {
// break;
// }
primesSpan[primeWriteIndex++] = candidate;
// currentPrime = primeEnumerator.Current;
// }
if (candidate > maxPrime)
{
maxPrime = candidate;
}
}
// if (!hasPrime || currentPrime != candidate)
// if (!Number.IsPrime(candidate))
// {
// markComposite();
// printResult(candidate, false, false, false);
// continue;
// }
if (primeWriteIndex < primesToTest.Count)
{
primesToTest.RemoveRange(primeWriteIndex, primesToTest.Count - primeWriteIndex);
}
primesSpan[primeWriteIndex++] = candidate;
if (primesToTest.Count == 0)
{
if (applyStartPrime)
{
Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor.");
}
else
{
Console.WriteLine("No prime candidates remain for --mersenne=bydivisor after filtering.");
}
if (candidate > maxPrime)
{
maxPrime = candidate;
}
}
return;
}
if (primeWriteIndex < primesToTest.Count)
{
primesToTest.RemoveRange(primeWriteIndex, primesToTest.Count - primeWriteIndex);
}
if (maxPrime <= 1UL)
{
Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor.");
return;
}
if (primesToTest.Count == 0)
{
if (applyStartPrime)
{
Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor.");
}
else
{
Console.WriteLine("No prime candidates remain for --mersenne=bydivisor after filtering.");
}
tester.ConfigureFromMaxPrime(maxPrime);
return;
}
int primeCount = primesToTest.Count;
ulong[] primeBatch = ArrayPool<ulong>.Shared.Rent(primeCount);
ulong[] allowedMaxBatch = ArrayPool<ulong>.Shared.Rent(primeCount);
List<ulong> filteredPrimes = new(primeCount);
if (maxPrime <= 1UL)
{
Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor.");
return;
}
try
{
Span<ulong> primeSpan = primeBatch.AsSpan(0, primeCount);
Span<ulong> allowedMaxSpan = allowedMaxBatch.AsSpan(0, primeCount);
tester.ConfigureFromMaxPrime(maxPrime);
for (int i = 0; i < primeCount; i++)
{
primeSpan[i] = primesToTest[i];
}
int primeCount = primesToTest.Count;
ulong[] primeBatch = pool.Rent(primeCount);
ulong[] allowedMaxBatch = pool.Rent(primeCount);
List<ulong> filteredPrimes = new(primeCount);
tester.PrepareCandidates(primeSpan, allowedMaxSpan);
Span<ulong> primeSpan = primeBatch.AsSpan(0, primeCount);
Span<ulong> allowedMaxSpan = allowedMaxBatch.AsSpan(0, primeCount);
for (int i = 0; i < primeCount; i++)
{
ulong prime = primeSpan[i];
ulong allowedMax = allowedMaxSpan[i];
for (int i = 0; i < primeCount; i++)
{
primeSpan[i] = primesToTest[i];
}
if (allowedMax < 3UL)
{
clearComposite();
printResult(prime, true, true, true);
continue;
}
tester.PrepareCandidates(primeSpan, allowedMaxSpan);
filteredPrimes.Add(prime);
}
}
finally
{
ArrayPool<ulong>.Shared.Return(allowedMaxBatch, clearArray: true);
ArrayPool<ulong>.Shared.Return(primeBatch, clearArray: true);
}
for (int i = 0; i < primeCount; i++)
{
ulong prime = primeSpan[i];
ulong allowedMax = allowedMaxSpan[i];
if (filteredPrimes.Count == 0)
{
if (applyStartPrime)
{
Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor.");
}
if (allowedMax < 3UL)
{
clearComposite();
printResult(prime, true, true, true);
continue;
}
return;
}
filteredPrimes.Add(prime);
}
int workerCount = threadCount <= 0 ? Environment.ProcessorCount : threadCount;
if (workerCount < 1)
{
workerCount = 1;
}
pool.Return(allowedMaxBatch, clearArray: true);
pool.Return(primeBatch, clearArray: true);
void ProcessPrime(ulong prime)
{
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
if (filteredPrimes.Count == 0)
{
if (applyStartPrime)
{
Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor.");
}
if (!isPrime)
{
markComposite();
printResult(prime, true, true, false);
return;
}
return;
}
clearComposite();
printResult(prime, true, divisorsExhausted, true);
}
int workerCount = threadCount <= 0 ? Environment.ProcessorCount : threadCount;
if (workerCount < 1)
{
workerCount = 1;
}
if (workerCount == 1)
{
foreach (ulong prime in filteredPrimes)
{
ProcessPrime(prime);
}
}
else
{
ParallelOptions options = new()
{
MaxDegreeOfParallelism = workerCount
};
void ProcessPrime(ulong prime)
{
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
if (!isPrime)
{
markComposite();
printResult(prime, true, true, false);
return;
}
clearComposite();
printResult(prime, true, divisorsExhausted, true);
}
if (workerCount == 1)
{
foreach (ulong prime in filteredPrimes)
{
ProcessPrime(prime);
}
}
else
{
ParallelOptions options = new()
{
MaxDegreeOfParallelism = workerCount
};
Parallel.ForEach(filteredPrimes, options, ProcessPrime);
}
}
Parallel.ForEach(filteredPrimes, options, ProcessPrime);
}
}
}
@@ -371,6 +371,10 @@ public static class MersennePrimeFactorTester
return 2UL;
}
var rng = new Random(1234567); // TODO: Replace this RNG with the deterministic span-based
// sequence measured faster in PollardRhoBenchmarks so we avoid
// per-call Random allocations.
ulong c, d, x, y, diff;
while (true)
{
@@ -379,8 +383,8 @@ public static class MersennePrimeFactorTester
return 0UL;
}
c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
c = (ulong)rng.Next(2, int.MaxValue);
x = (ulong)rng.Next(2, int.MaxValue);
y = x;
d = 1UL;
@@ -519,6 +523,9 @@ public static class MersennePrimeFactorTester
return 2;
}
var rng = new Random(1234567); // TODO: Replace with the deterministic UInt128 sequence from
// PollardRho128Benchmarks so we eliminate Random allocations.
UInt128 c, d, x, y, diff;
while (true)
{
@@ -527,8 +534,8 @@ public static class MersennePrimeFactorTester
return 0;
}
c = (DeterministicRandom.NextUInt128() % (n - 1)) + 1;
x = (DeterministicRandom.NextUInt128() % (n - 2)) + 2;
c = (UInt128)rng.NextInt64(2, long.MaxValue);
x = (UInt128)rng.NextInt64(2, long.MaxValue);
y = x;
d = 1;
@@ -1,3 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
@@ -1,80 +0,0 @@
using System.Buffers;
namespace PerfectNumbers.Core
{
public sealed class PartialFactorResult
{
[ThreadStatic]
private static PartialFactorResult? s_poolHead;
public FactorEntry[]? Factors;
private PartialFactorResult? _next;
public ulong Cofactor;
public bool FullyFactored;
public int Count;
private PartialFactorResult()
{
}
public static PartialFactorResult Rent(in FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count)
{
PartialFactorResult? instance = s_poolHead;
if (instance is null)
{
instance = new PartialFactorResult();
}
else
{
s_poolHead = instance._next;
}
instance._next = null;
instance.Factors = factors;
instance.Cofactor = cofactor;
instance.FullyFactored = fullyFactored;
instance.Count = count;
return instance;
}
public static readonly PartialFactorResult Empty = new()
{
Cofactor = 1UL,
FullyFactored = true,
};
public PartialFactorResult WithAdditionalPrime(ulong prime)
{
FactorEntry[]? source = Factors;
if (source is null || Count == 0)
{
FactorEntry[] local = ArrayPool<FactorEntry>.Shared.Rent(1);
local[0] = new FactorEntry(prime, 1);
return Rent(local, 1UL, true, 1);
}
FactorEntry[] extended = ArrayPool<FactorEntry>.Shared.Rent(Count + 1);
Array.Copy(source, 0, extended, 0, Count);
extended[Count] = new FactorEntry(prime, 1);
Span<FactorEntry> span = extended.AsSpan(0, Count + 1);
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
return Rent(extended, 1UL, true, Count + 1);
}
public void Dispose()
{
if (this != Empty)
{
FactorEntry[]? factors = Factors;
if (factors is not null)
{
Factors = null;
ThreadStaticPools.FactorEntryPool.Return(factors, clearArray: false);
}
_next = s_poolHead;
s_poolHead = this;
}
}
}
}
@@ -8,7 +8,6 @@ public static class PerfectNumberConstants
public const int MaxQForDivisorCycles = 4_000_000;
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
public const int MaxOddPowersCount = 128;
public const int PooledArrayThreshold = 64;
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
// divisor-cycle datasets we generate offline.
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
File diff suppressed because it is too large. Load diff
@@ -1,45 +0,0 @@
using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator
{
private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2;
private const int GpuSmallPrimeFactorSlots = 64;
private static bool TryPopulateSmallPrimeFactorsGpu(ulong value, uint limit, Dictionary<ulong, int> counts, out ulong remaining)
{
ulong[] primeBufferArray = ThreadStaticPools.UlongPool.Rent(GpuSmallPrimeFactorSlots);
int[] exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots);
Span<ulong> primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
Span<int> exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
primeBuffer.Clear();
exponentBuffer.Clear();
if (!PrimeOrderGpuHeuristics.TryPartialFactor(value, limit, primeBuffer, exponentBuffer, out int factorCount, out ulong gpuRemaining, out _))
{
remaining = value;
ThreadStaticPools.UlongPool.Return(primeBufferArray);
ThreadStaticPools.IntPool.Return(exponentBufferArray);
return false;
}
remaining = gpuRemaining;
for (int i = 0; i < factorCount; i++)
{
ulong primeValue = primeBuffer[i];
int exponent = exponentBuffer[i];
if (primeValue == 0UL || exponent == 0)
{
continue;
}
counts[primeValue] = exponent;
}
ThreadStaticPools.UlongPool.Return(primeBufferArray);
ThreadStaticPools.IntPool.Return(exponentBufferArray);
return true;
}
}
@@ -1,127 +1,102 @@
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Threading;
using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator
{
[ThreadStatic]
private static bool s_pow2ModeInitialized;
[ThreadStatic]
private static bool s_allowGpuPow2;
[ThreadStatic]
private static PrimeOrderHeuristicDevice s_deviceMode;
[ThreadStatic]
private static bool s_debugLoggingEnabled;
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
private readonly struct Pow2ModeScope
internal readonly struct PrimeOrderResultWide
{
public PrimeOrderResultWide(PrimeOrderStatus status, UInt128 order)
{
private readonly bool _previousInitialized;
private readonly bool _previousAllow;
private readonly PrimeOrderHeuristicDevice _previousDevice;
public Pow2ModeScope(PrimeOrderHeuristicDevice device)
{
_previousInitialized = s_pow2ModeInitialized;
_previousAllow = s_allowGpuPow2;
_previousDevice = s_deviceMode;
s_pow2ModeInitialized = true;
s_allowGpuPow2 = device == PrimeOrderHeuristicDevice.Gpu;
s_deviceMode = device;
}
public void Dispose()
{
s_allowGpuPow2 = _previousAllow;
s_pow2ModeInitialized = _previousInitialized;
s_deviceMode = _previousDevice;
}
Status = status;
Order = order;
}
private readonly struct DebugLoggingScope
public PrimeOrderStatus Status { get; }
public UInt128 Order { get; }
}
public static PrimeOrderResultWide Calculate(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
=> Calculate(prime, previousOrder, config, PrimeOrderHeuristicDevice.Gpu);
public static PrimeOrderResultWide Calculate(
UInt128 prime,
UInt128? previousOrder,
PrimeOrderSearchConfig config,
PrimeOrderHeuristicDevice device)
{
bool enableGpuPow2 = device == PrimeOrderHeuristicDevice.Gpu;
using var scope = UsePow2Mode(device, enableGpuPow2);
MontgomeryDivisorData divisorData;
if (prime <= ulong.MaxValue)
{
private readonly bool _previous;
public DebugLoggingScope(bool enabled)
ulong? previous = null;
if (previousOrder.HasValue)
{
UInt128 previousValue = previousOrder.Value;
if (previousValue <= ulong.MaxValue)
{
_previous = s_debugLoggingEnabled;
s_debugLoggingEnabled = enabled;
previous = (ulong)previousValue;
}
public void Dispose()
else
{
s_debugLoggingEnabled = _previous;
previous = ulong.MaxValue;
}
}
ulong prime64 = (ulong)prime;
divisorData = MontgomeryDivisorData.FromModulus(prime64);
PrimeOrderResult result = Calculate(prime64, previous, divisorData, config, device, enableGpuPow2);
return new PrimeOrderResultWide(result.Status, result.Order);
}
else
{
divisorData = default;
}
private static Pow2ModeScope UsePow2Mode(PrimeOrderHeuristicDevice device) => new(device);
return CalculateWideInternal(prime, previousOrder, divisorData, config);
}
private static DebugLoggingScope UseDebugLogging(bool enabled) => new(enabled);
[Conditional("DEBUG")]
private static void DebugLog(string message)
{
if (s_debugLoggingEnabled)
{
Console.WriteLine(message);
}
}
[Conditional("DEBUG")]
private static void DebugLog(Func<string> messageFactory)
{
if (s_debugLoggingEnabled)
{
Console.WriteLine(messageFactory());
}
}
private static UInt128 CalculateWideInternal(in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
private static PrimeOrderResultWide CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
{
if (prime <= UInt128.One)
{
return UInt128.One;
return new PrimeOrderResultWide(PrimeOrderStatus.Found, UInt128.One);
}
if (prime == (UInt128)3UL)
{
return (UInt128)2UL;
return new PrimeOrderResultWide(PrimeOrderStatus.Found, (UInt128)2UL);
}
UInt128 phi = prime - UInt128.One;
var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
using var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
UInt128 result;
if (phiFactors.Factors is null)
{
result = FinishStrictlyWide(prime, divisorData);
}
else
{
result = RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
return FinishStrictlyWide(prime, divisorData, config.Mode);
}
debugScope.Dispose();
return result;
return RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
}
private static UInt128 RunHeuristicPipelineWide(
in UInt128 prime,
in UInt128? previousOrder,
private static PrimeOrderResultWide RunHeuristicPipelineWide(
UInt128 prime,
UInt128? previousOrder,
in MontgomeryDivisorData divisorData,
in PrimeOrderSearchConfig config,
in UInt128 phi,
in PartialFactorResult128 phiFactors)
PrimeOrderSearchConfig config,
UInt128 phi,
PartialFactorResult128 phiFactors)
{
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
{
return phi;
return new PrimeOrderResultWide(PrimeOrderStatus.Found, phi);
}
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
@@ -129,28 +104,30 @@ internal static partial class PrimeOrderCalculator
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
{
return candidateOrder;
return new PrimeOrderResultWide(PrimeOrderStatus.Found, candidateOrder);
}
if (config.Mode == PrimeOrderMode.Strict)
{
return FinishStrictlyWide(prime, divisorData);
return FinishStrictlyWide(prime, divisorData, PrimeOrderMode.Strict);
}
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
{
return order;
return new PrimeOrderResultWide(PrimeOrderStatus.Found, order);
}
return candidateOrder;
return FinishStrictlyWide(prime, divisorData, config.Mode);
}
private static UInt128 FinishStrictlyWide(in UInt128 prime, in MontgomeryDivisorData divisorData)
private static PrimeOrderResultWide FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData, PrimeOrderMode mode)
{
return CalculateByFactorizationWide(prime, divisorData);
UInt128 strictOrder = CalculateByFactorizationWide(prime, divisorData);
PrimeOrderStatus status = mode == PrimeOrderMode.Strict ? PrimeOrderStatus.Found : PrimeOrderStatus.HeuristicUnresolved;
return new PrimeOrderResultWide(status, strictOrder);
}
private static bool TrySpecialMaxWide(in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
{
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
int length = factors.Count;
@@ -167,7 +144,7 @@ internal static partial class PrimeOrderCalculator
return true;
}
private static UInt128 InitializeStartingOrderWide(in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData divisorData)
private static UInt128 InitializeStartingOrderWide(UInt128 prime, UInt128 phi, in MontgomeryDivisorData divisorData)
{
UInt128 order = phi;
UInt128 mod8 = prime & (UInt128)7UL;
@@ -183,20 +160,19 @@ 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(UInt128 order, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
{
ArrayPool<FactorEntry128> pool = ThreadStaticPools.FactorEntry128Pool;
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
int length = factors.Count;
// TODO: This should never trigger from production code - check
// if (length == 0)
// {
// return order;
// }
FactorEntry128[]? tempArray = pool.Rent(length + 1);
FactorEntry128[]? tempArray = null;
try
{
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
int length = factors.Count;
if (length == 0)
{
return order;
}
tempArray = ArrayPool<FactorEntry128>.Shared.Rent(length + 1);
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
factorSpan.CopyTo(buffer);
@@ -232,11 +208,14 @@ internal static partial class PrimeOrderCalculator
}
finally
{
pool.Return(tempArray, clearArray: false);
if (tempArray is not null)
{
ArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
}
}
}
private static bool TryConfirmOrderWide(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
private static bool TryConfirmOrderWide(UInt128 prime, UInt128 order, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
{
if (order == UInt128.Zero)
{
@@ -294,11 +273,11 @@ internal static partial class PrimeOrderCalculator
}
private static bool TryHeuristicFinishWide(
in UInt128 prime,
in UInt128 order,
in UInt128? previousOrder,
UInt128 prime,
UInt128 order,
UInt128? previousOrder,
in MontgomeryDivisorData divisorData,
in PrimeOrderSearchConfig config,
PrimeOrderSearchConfig config,
out UInt128 result)
{
result = UInt128.Zero;
@@ -370,7 +349,7 @@ internal static partial class PrimeOrderCalculator
return false;
}
private static void SortCandidatesWide(UInt128 prime, in UInt128? previousOrder, List<UInt128> candidates)
private static void SortCandidatesWide(UInt128 prime, UInt128? previousOrder, List<UInt128> candidates)
{
UInt128 previous = previousOrder ?? UInt128.Zero;
int previousGroup = previousOrder.HasValue ? GetGroupWide(previousOrder.Value, prime) : 1;
@@ -398,9 +377,9 @@ internal static partial class PrimeOrderCalculator
}
private static CandidateKey128 BuildKeyWide(
in UInt128 value,
in UInt128 prime,
in UInt128 previous,
UInt128 value,
UInt128 prime,
UInt128 previous,
int previousGroup,
bool hasPrevious)
{
@@ -422,7 +401,7 @@ internal static partial class PrimeOrderCalculator
return new CandidateKey128(primary, false, distance, false, value);
}
private static int CompareComponents(bool descendingX, in UInt128 valueX, bool descendingY, in UInt128 valueY)
private static int CompareComponents(bool descendingX, UInt128 valueX, bool descendingY, UInt128 valueY)
{
if (descendingX == descendingY)
{
@@ -447,7 +426,7 @@ internal static partial class PrimeOrderCalculator
return valueX > valueY ? 1 : -1;
}
private static int GetGroupWide(in UInt128 value, in UInt128 prime)
private static int GetGroupWide(UInt128 value, UInt128 prime)
{
UInt128 threshold1 = prime >> 3;
if (value <= threshold1)
@@ -470,7 +449,7 @@ internal static partial class PrimeOrderCalculator
return 0;
}
private static void BuildCandidatesWide(in UInt128 order, FactorEntry128[] factors, int count, List<UInt128> candidates, int limit)
private static void BuildCandidatesWide(UInt128 order, FactorEntry128[] factors, int count, List<UInt128> candidates, int limit)
{
if (count == 0)
{
@@ -483,10 +462,10 @@ internal static partial class PrimeOrderCalculator
}
private static void BuildCandidatesRecursiveWide(
in UInt128 order,
in ReadOnlySpan<FactorEntry128> factors,
UInt128 order,
ReadOnlySpan<FactorEntry128> factors,
int index,
in UInt128 divisorProduct,
UInt128 divisorProduct,
List<UInt128> candidates,
int limit)
{
@@ -542,7 +521,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(UInt128 prime, UInt128 candidate, PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData)
{
PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
if (factorization.Factors is null)
@@ -595,7 +574,7 @@ internal static partial class PrimeOrderCalculator
return true;
}
private static PartialFactorResult128 PartialFactorWide(in UInt128 value, in PrimeOrderSearchConfig config)
private static PartialFactorResult128 PartialFactorWide(UInt128 value, PrimeOrderSearchConfig config)
{
if (value <= UInt128.One)
{
@@ -636,12 +615,12 @@ internal static partial class PrimeOrderCalculator
if (config.PollardRhoMilliseconds > 0 && pending.Count > 0)
{
long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds);
Stopwatch stopwatch = Stopwatch.StartNew();
long budgetTicks = TimeSpan.FromMilliseconds(config.PollardRhoMilliseconds).Ticks;
Stack<UInt128> stack = new();
stack.Push(remaining);
pending.Clear();
long timestamp = 0L; // reused for deadline checks.
while (stack.Count > 0)
{
UInt128 composite = stack.Pop();
@@ -656,14 +635,13 @@ internal static partial class PrimeOrderCalculator
continue;
}
timestamp = Stopwatch.GetTimestamp();
if (timestamp > deadlineTimestamp)
if (stopwatch.ElapsedTicks > budgetTicks)
{
pending.Add(composite);
continue;
}
if (!TryPollardRhoWide(composite, deadlineTimestamp, out UInt128 factor))
if (!TryPollardRhoWide(composite, stopwatch, budgetTicks, out UInt128 factor))
{
pending.Add(composite);
continue;
@@ -696,8 +674,7 @@ internal static partial class PrimeOrderCalculator
return new PartialFactorResult128(null, value, false, 0);
}
ArrayPool<FactorEntry128>? pool = null;
FactorEntry128[]? rented = counts.Count > 0 ? (pool = ThreadStaticPools.FactorEntry128Pool).Rent(counts.Count) : null;
FactorEntry128[]? rented = counts.Count > 0 ? ArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null;
int index = 0;
if (rented is not null)
{
@@ -709,20 +686,20 @@ internal static partial class PrimeOrderCalculator
}
FactorEntry128[]? resultArray = null;
if (pool is not null)
if (rented is not null)
{
Span<FactorEntry128> span = rented.AsSpan(0, index);
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
resultArray = new FactorEntry128[index];
span.CopyTo(resultArray);
pool.Return(rented!, clearArray: false);
ArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false);
}
bool fullyFactored = cofactor == UInt128.One;
return new PartialFactorResult128(resultArray, cofactor, fullyFactored, index);
}
private static UInt128 PopulateSmallPrimeFactorsCpuWide(in UInt128 value, uint limit, Dictionary<UInt128, int> counts)
private static UInt128 PopulateSmallPrimeFactorsCpuWide(UInt128 value, uint limit, Dictionary<UInt128, int> counts)
{
UInt128 remaining = value;
uint[] primes = PrimesGenerator.SmallPrimes;
@@ -762,7 +739,7 @@ internal static partial class PrimeOrderCalculator
return remaining;
}
private static bool TryPollardRhoWide(in UInt128 n, long deadlineTimestamp, out UInt128 factor)
private static bool TryPollardRhoWide(UInt128 n, Stopwatch stopwatch, long budgetTicks, out UInt128 factor)
{
factor = UInt128.Zero;
if ((n & UInt128.One) == UInt128.Zero)
@@ -774,12 +751,10 @@ internal static partial class PrimeOrderCalculator
UInt128 c = UInt128.One;
UInt128 x = (UInt128)2UL;
UInt128 y = x;
long timestamp = 0L; // reused for deadline checks.
while (true)
{
timestamp = Stopwatch.GetTimestamp();
if (timestamp > deadlineTimestamp)
if (stopwatch.ElapsedTicks > budgetTicks)
{
return false;
}
@@ -806,7 +781,7 @@ internal static partial class PrimeOrderCalculator
}
}
private static UInt128 AdvancePolynomialWide(in UInt128 x, in UInt128 c, in UInt128 modulus)
private static UInt128 AdvancePolynomialWide(UInt128 x, UInt128 c, UInt128 modulus)
{
BigInteger value = (BigInteger)x;
value = (value * value + (BigInteger)c) % (BigInteger)modulus;
@@ -844,7 +819,7 @@ internal static partial class PrimeOrderCalculator
}
}
private static int TrailingZeroCountWide(in UInt128 value)
private static int TrailingZeroCountWide(UInt128 value)
{
if (value == UInt128.Zero)
{
@@ -900,7 +875,7 @@ internal static partial class PrimeOrderCalculator
return order;
}
private static void FactorCompletelyWide(in UInt128 value, Dictionary<UInt128, int> counts)
private static void FactorCompletelyWide(UInt128 value, Dictionary<UInt128, int> counts)
{
if (value <= UInt128.One)
{
@@ -918,7 +893,7 @@ internal static partial class PrimeOrderCalculator
FactorCompletelyWide(value / factor, counts);
}
private static UInt128 PollardRhoWide(in UInt128 n)
private static UInt128 PollardRhoWide(UInt128 n)
{
if ((n & UInt128.One) == UInt128.Zero)
{
@@ -951,7 +926,7 @@ internal static partial class PrimeOrderCalculator
}
}
private static bool IsPrimeWide(in UInt128 value)
private static bool IsPrimeWide(UInt128 value)
{
if (value <= ulong.MaxValue)
{
@@ -1031,7 +1006,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(UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData)
{
if (modulus == UInt128.One)
{
@@ -1056,12 +1031,17 @@ internal static partial class PrimeOrderCalculator
{
return wideRemainder;
}
PrimeOrderGpuHeuristics.ReportPow2Failure(
"wide prime order pow2 check",
modulus,
wideStatus);
}
return exponent.Pow2MontgomeryModWindowed(modulus);
}
private static void AddFactor(Dictionary<UInt128, int> counts, in UInt128 prime, int exponent)
private static void AddFactor(Dictionary<UInt128, int> counts, UInt128 prime, int exponent)
{
if (counts.TryGetValue(prime, out int existing))
{
@@ -1073,30 +1053,60 @@ internal static partial class PrimeOrderCalculator
}
}
private readonly struct CandidateKey128(int primary, bool secondaryDescending, in UInt128 secondary, bool tertiaryDescending, in UInt128 tertiary)
{
public int Primary { get; } = primary;
private readonly struct CandidateKey128
{
public CandidateKey128(int primary, bool secondaryDescending, UInt128 secondary, bool tertiaryDescending, UInt128 tertiary)
{
Primary = primary;
SecondaryDescending = secondaryDescending;
Secondary = secondary;
TertiaryDescending = tertiaryDescending;
Tertiary = tertiary;
}
public bool SecondaryDescending { get; } = secondaryDescending;
public int Primary { get; }
public UInt128 Secondary { get; } = secondary;
public bool SecondaryDescending { get; }
public bool TertiaryDescending { get; } = tertiaryDescending;
public UInt128 Secondary { get; }
public UInt128 Tertiary { get; } = tertiary;
}
public bool TertiaryDescending { get; }
private readonly struct PartialFactorResult128(FactorEntry128[]? factors, UInt128 cofactor, bool fullyFactored, int count)
{
public FactorEntry128[]? Factors { get; } = factors;
public UInt128 Tertiary { get; }
}
public UInt128 Cofactor { get; } = cofactor;
private readonly struct FactorEntry128
{
public FactorEntry128(UInt128 value, int exponent)
{
Value = value;
Exponent = exponent;
}
public bool FullyFactored { get; } = fullyFactored;
public UInt128 Value { get; }
public int Count { get; } = count;
public int Exponent { get; }
}
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
private readonly struct PartialFactorResult128
{
public PartialFactorResult128(FactorEntry128[]? factors, UInt128 cofactor, bool fullyFactored, int count)
{
Factors = factors;
Cofactor = cofactor;
FullyFactored = fullyFactored;
Count = count;
}
public FactorEntry128[]? Factors { get; }
public UInt128 Cofactor { get; }
public bool FullyFactored { get; }
public int Count { get; }
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
public PartialFactorResult128 WithAdditionalPrime(UInt128 prime)
{
File diff suppressed because it is too large. Load diff
-320
View File
@@ -1,320 +0,0 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Cpu;
namespace PerfectNumbers.Core
{
public readonly struct ThreadStaticPools
{
[ThreadStatic]
private static ArrayPool<FactorEntry>? _factorEntryPool;
public static ArrayPool<FactorEntry> FactorEntryPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _factorEntryPool ??= ArrayPool<FactorEntry>.Create();
}
}
[ThreadStatic]
private static ArrayPool<FactorEntry128>? _factorEntry128Pool;
public static ArrayPool<FactorEntry128> FactorEntry128Pool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _factorEntry128Pool ??= ArrayPool<FactorEntry128>.Create();
}
}
[ThreadStatic]
private static ArrayPool<KeyValuePair<ulong, int>>? _factorKeyValuePairPool;
public static ArrayPool<KeyValuePair<ulong, int>> FactorKeyValuePairPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _factorKeyValuePairPool ??= ArrayPool<KeyValuePair<ulong, int>>.Create();
}
}
[ThreadStatic]
private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static MersenneCpuDivisorScanSession? RentMersenneCpuDivisorSession()
{
MersenneCpuDivisorScanSession? session = _mersenneCpuDivisorSession;
if (session is null)
{
return null;
}
_mersenneCpuDivisorSession = null;
session.Reset();
return session;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ReturnMersenneCpuDivisorSession(MersenneCpuDivisorScanSession session)
{
_mersenneCpuDivisorSession = session;
}
[ThreadStatic]
private static ArrayPool<ulong>? _ulongPool;
public static ArrayPool<ulong> UlongPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _ulongPool ??= ArrayPool<ulong>.Create();
}
}
[ThreadStatic]
private static ArrayPool<int>? _intPool;
public static ArrayPool<int> IntPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _intPool ??= ArrayPool<int>.Create();
}
}
[ThreadStatic]
private static List<List<ulong>>? _ulongListPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<ulong> RentUlongList(int capacityHint)
{
List<List<ulong>>? pool = _ulongListPool;
if (pool is not null && pool.Count > 0)
{
int lastIndex = pool.Count - 1;
List<ulong> list = pool[lastIndex];
pool.RemoveAt(lastIndex);
if (list.Count > 0)
{
list.Clear();
}
if (list.Capacity < capacityHint)
{
list.Capacity = capacityHint;
}
return list;
}
return new List<ulong>(capacityHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnUlongList(List<ulong> list)
{
List<List<ulong>>? pool = _ulongListPool;
if (pool is null)
{
pool = new List<List<ulong>>(4);
_ulongListPool = pool;
}
pool.Add(list);
}
[ThreadStatic]
private static List<Stack<ulong>>? _ulongStackPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stack<ulong> RentUlongStack(int capacityHint)
{
List<Stack<ulong>>? pool = _ulongStackPool;
if (pool is not null && pool.Count > 0)
{
int lastIndex = pool.Count - 1;
Stack<ulong> stack = pool[lastIndex];
pool.RemoveAt(lastIndex);
if (stack.Count > 0)
{
stack.Clear();
}
if (stack.Count < capacityHint)
{
stack.EnsureCapacity(capacityHint);
}
return stack;
}
return new Stack<ulong>(capacityHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnUlongStack(Stack<ulong> stack)
{
List<Stack<ulong>>? pool = _ulongStackPool;
if (pool is null)
{
pool = new List<Stack<ulong>>(4);
_ulongStackPool = pool;
}
pool.Add(stack);
}
[ThreadStatic]
private static List<Dictionary<ulong, int>>? _ulongIntDictionaryPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<ulong, int> RentUlongIntDictionary(int capacityHint)
{
List<Dictionary<ulong, int>>? pool = _ulongIntDictionaryPool;
if (pool is not null && pool.Count > 0)
{
int lastIndex = pool.Count - 1;
Dictionary<ulong, int> dictionary = pool[lastIndex];
pool.RemoveAt(lastIndex);
if (dictionary.Count > 0)
{
dictionary.Clear();
}
if (dictionary.Count < capacityHint)
{
dictionary.EnsureCapacity(capacityHint);
}
return dictionary;
}
return new Dictionary<ulong, int>(capacityHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnUlongIntDictionary(Dictionary<ulong, int> dictionary)
{
List<Dictionary<ulong, int>>? pool = _ulongIntDictionaryPool;
if (pool is null)
{
pool = new List<Dictionary<ulong, int>>(4);
_ulongIntDictionaryPool = pool;
}
pool.Add(dictionary);
}
[ThreadStatic]
private static Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? _mersenneFactorCacheDictionary;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> RentMersenneFactorCacheDictionary()
{
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache = _mersenneFactorCacheDictionary;
if (cache is null)
{
return new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
}
_mersenneFactorCacheDictionary = null;
cache.Clear();
return cache;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnMersenneFactorCacheDictionary(Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> cache)
{
for (Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>.Enumerator enumerator = cache.GetEnumerator(); enumerator.MoveNext();)
{
enumerator.Current.Value.ReturnToPool();
}
cache.Clear();
_mersenneFactorCacheDictionary = cache;
}
[ThreadStatic]
private static MersenneDivisorCycles.FactorCacheEntry? _factorCacheEntryPoolHead;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MersenneDivisorCycles.FactorCacheEntry RentFactorCacheEntry()
{
MersenneDivisorCycles.FactorCacheEntry? entry = _factorCacheEntryPoolHead;
if (entry is null)
{
entry = new MersenneDivisorCycles.FactorCacheEntry();
}
else
{
_factorCacheEntryPoolHead = entry.Next;
}
entry.Reset();
return entry;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnFactorCacheEntry(MersenneDivisorCycles.FactorCacheEntry entry)
{
entry.Next = _factorCacheEntryPoolHead;
_factorCacheEntryPoolHead = entry;
}
[ThreadStatic]
private static Dictionary<ulong, int>? _factorCountDictionary;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<ulong, int> RentFactorCountDictionary()
{
Dictionary<ulong, int>? dictionary = _factorCountDictionary;
if (dictionary is null)
{
return new Dictionary<ulong, int>(8);
}
_factorCountDictionary = null;
dictionary.Clear();
return dictionary;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnFactorCountDictionary(Dictionary<ulong, int> dictionary)
{
dictionary.Clear();
_factorCountDictionary = dictionary;
}
[ThreadStatic]
private static Dictionary<ulong, int>? _factorScratchDictionary;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<ulong, int> RentFactorScratchDictionary()
{
Dictionary<ulong, int>? dictionary = _factorScratchDictionary;
if (dictionary is null)
{
return new Dictionary<ulong, int>(8);
}
_factorScratchDictionary = null;
dictionary.Clear();
return dictionary;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnFactorScratchDictionary(Dictionary<ulong, int> dictionary)
{
dictionary.Clear();
_factorScratchDictionary = dictionary;
}
}
}
+9 -6
View File
@@ -1,3 +1,4 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Gpu;
@@ -217,7 +218,7 @@ public static class UInt128Extensions
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
{
if (bitIndex >= 64)
{
@@ -228,16 +229,18 @@ public static class UInt128Extensions
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
{
ulong mask = (1UL << windowBitCount) - 1UL;
if (windowStart != 0)
{
exponent.ShiftRight(windowStart);
return exponent.Low & mask;
GpuUInt128 shifted = exponent;
shifted.ShiftRight(windowStart);
ulong mask = (1UL << windowBitCount) - 1UL;
return shifted.Low & mask;
}
return exponent.Low & mask;
ulong directMask = (1UL << windowBitCount) - 1UL;
return exponent.Low & directMask;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
File diff suppressed because it is too large. Load diff