Compare commits
71
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef078df83d | ||
|
|
645969309c | ||
|
|
01ac80466b | ||
|
|
2b0d7278db | ||
|
|
1c0bd3f3d3 | ||
|
|
4ed9db7d0b | ||
|
|
99a7ec5b70 | ||
|
|
ca0cadc8a3 | ||
|
|
70eca5126b | ||
|
|
0be9596bd5 | ||
|
|
4f68c90d88 | ||
|
|
7aaccb5feb | ||
|
|
c334b9e878 | ||
|
|
e7ac18ea0b | ||
|
|
ef5a77cedd | ||
|
|
58c546cd87 | ||
|
|
8054f9fe59 | ||
|
|
b2ab47e0ee | ||
|
|
74e9e88977 | ||
|
|
f196f1fb96 | ||
|
|
a1a9c5f033 | ||
|
|
0bae2cb3e6 | ||
|
|
9e0c90a281 | ||
|
|
40bef43f0e | ||
|
|
ebfdc7d8b8 | ||
|
|
bcf50cf763 | ||
|
|
ce8c3a660a | ||
|
|
321157cf65 | ||
|
|
e1d4bbf12d | ||
|
|
77cc1fc280 | ||
|
|
668e161554 | ||
|
|
ce4c59beaa | ||
|
|
6bd8945565 | ||
|
|
99c9ddf73d | ||
|
|
32de7ebc75 | ||
|
|
fd0c978079 | ||
|
|
a19cbd245d | ||
|
|
64c08f9b49 | ||
|
|
79d821c475 | ||
|
|
adac9ff5f9 | ||
|
|
4ab70ec4a8 | ||
|
|
9a81f5f685 | ||
|
|
686d482417 | ||
|
|
85a98eb009 | ||
|
|
5a66abe672 | ||
|
|
fbe9a98b42 | ||
|
|
4afe0e356e | ||
|
|
c45b3951c3 | ||
|
|
632957e4f4 | ||
|
|
6dc26336c6 | ||
|
|
6a4ee0e2c5 | ||
|
|
85634da6eb | ||
|
|
26abc2dfa8 | ||
|
|
6918bd28ff | ||
|
|
cd1debe9d6 | ||
|
|
2eb622030e | ||
|
|
762b9bd9f3 | ||
|
|
b27c8bffa7 | ||
|
|
c87f88fd88 | ||
|
|
2dd5a679e2 | ||
|
|
e0a5eea62d | ||
|
|
48127845b5 | ||
|
|
31e95e5a8b | ||
|
|
16181903a1 | ||
|
|
bd8686b86b | ||
|
|
894a3d8af2 | ||
|
|
ac846df7d9 | ||
|
|
581da24b2f | ||
|
|
0543306f31 | ||
|
|
20120f5bc0 | ||
|
|
ce1355c749 |
No files matched your search
Vendored
+16
-3
@@ -20,9 +20,22 @@
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Debug",
|
||||
"program": "${workspaceFolder}/${input:projectName}/bin/Debug/net8.0/${input:projectName}.dll",
|
||||
"args": [
|
||||
"--increment=add --mersenne=residue --threads=128 --gpu-scan-batch=512000 --primes-device=gpu --mersenne-device=gpu --mod-reduction=barrett128 --block-size=10000000 --gpu-prime-threads=32 --write-batch-size=10000 --divisor-cycles-device=gpu"
|
||||
],
|
||||
"args": [
|
||||
"--increment=add",
|
||||
"--mersenne=bydivisor",
|
||||
"--threads=1",
|
||||
"--gpu-scan-batch=256000",
|
||||
"--primes-device=gpu",
|
||||
"--mersenne-device=gpu",
|
||||
"--mod-reduction=barrett128",
|
||||
"--block-size=1000000",
|
||||
"--gpu-prime-threads=64",
|
||||
"--write-batch-size=10000",
|
||||
"--divisor-cycles-device=gpu",
|
||||
"--prime=31",
|
||||
"--results-prefix=test-",
|
||||
"--filter-p=sorted-primes-passed-residue-_even_perfect_bit_scan_inc-add_thr-16_blk-10000000_mers-incremental_mersdev-gpu_ntt-reference_red-barrett128_primesdev-gpu_order-off_orderdev-gpu_gcd-off_modfix-off_gputh-64_llslice-32_scanb-512000_warm-5000000.csv"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"stopAtEntry": false,
|
||||
"console": "internalConsole"
|
||||
|
||||
Vendored
+2
@@ -3,8 +3,10 @@
|
||||
"chatgpt.openOnStartup": true,
|
||||
"cSpell.words": [
|
||||
"CCCCCCCDUL",
|
||||
"cofactor",
|
||||
"ILGPU",
|
||||
"mers",
|
||||
"Mersenne",
|
||||
"primality",
|
||||
"suff"
|
||||
]
|
||||
|
||||
+3124
File diff suppressed because it is too large.
Load diff
@@ -50,7 +50,7 @@ public class GpuPow2ModBenchmarks
|
||||
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
||||
}
|
||||
|
||||
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, in GpuUInt128 modulus)
|
||||
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.IsZero || modulus == GpuUInt128.One)
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
ulong smallModulus = NextSmallOddModulus();
|
||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
_smallExponents[i] = NextSmallExponent();
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus);
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
|
||||
@@ -52,14 +52,31 @@ 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 Baseline()
|
||||
public ulong BaselineCpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
|
||||
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);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -69,14 +86,31 @@ 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 BaselineWithKnownCycle()
|
||||
public ulong BaselineWithKnownCycleCpu()
|
||||
{
|
||||
GetData(out var exponents, out var divisors, out var cycles);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[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]);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
|
||||
@@ -22,318 +22,390 @@ 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 MontgomeryWithoutCycle()
|
||||
{
|
||||
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 MontgomeryWithoutCycleCpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
|
||||
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 MontgomeryWithPrecomputedCycle()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||
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 MontgomeryWithoutCycleGpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
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 MontgomeryWithGpuCycleComputation()
|
||||
{
|
||||
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 MontgomeryWithPrecomputedCycleCpu()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||
ulong checksum = 0UL;
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
||||
}
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(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 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);
|
||||
}
|
||||
|
||||
|
||||
[Benchmark]
|
||||
public ulong MontgomeryWithHeuristicCycleComputation()
|
||||
{
|
||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||
ulong checksum = 0UL;
|
||||
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);
|
||||
}
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
||||
}
|
||||
// When the below is uncommented, the benchmark never completes due to the primality test cost on very large random set.
|
||||
|
||||
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)
|
||||
{
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// }
|
||||
// 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
|
||||
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
||||
modulus,
|
||||
_previousPrimeOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
ulong order = PrimeOrderCalculator.Calculate(
|
||||
modulus,
|
||||
_previousPrimeOrder,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
if (orderResult.Order != 0UL)
|
||||
{
|
||||
_previousPrimeOrder = orderResult.Order;
|
||||
return orderResult.Order;
|
||||
}
|
||||
if (order != 0UL)
|
||||
{
|
||||
_previousPrimeOrder = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
_previousPrimeOrder = null;
|
||||
_previousPrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
|
||||
}
|
||||
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
|
||||
PrimeOrderCalculator.PrimeOrderResultWide orderResult = PrimeOrderCalculator.Calculate(
|
||||
modulus,
|
||||
_previousWidePrimeOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
UInt128 order = PrimeOrderCalculator.Calculate(
|
||||
modulus,
|
||||
_previousWidePrimeOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
if (orderResult.Order != UInt128.Zero)
|
||||
{
|
||||
_previousWidePrimeOrder = orderResult.Order;
|
||||
return orderResult.Order;
|
||||
}
|
||||
if (order != UInt128.Zero)
|
||||
{
|
||||
_previousWidePrimeOrder = order;
|
||||
return 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
||||
return (UInt128)result;
|
||||
}
|
||||
|
||||
private static bool ShouldUseSingleBit(in GpuUInt128 exponent)
|
||||
private static bool ShouldUseSingleBit(GpuUInt128 exponent)
|
||||
{
|
||||
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
||||
{
|
||||
@@ -257,7 +257,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
||||
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||
{
|
||||
if (bitIndex >= 64)
|
||||
{
|
||||
@@ -267,7 +267,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
||||
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
||||
}
|
||||
|
||||
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
{
|
||||
if (windowStart != 0)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,6 @@
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Running;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -7,15 +8,16 @@ public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary);
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||
// var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator);
|
||||
// BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||
|
||||
// Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new()
|
||||
// {
|
||||
// Scale = Pow2MontgomeryModCycleComputationBenchmarks.InputScale.Large
|
||||
// };
|
||||
Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new()
|
||||
{
|
||||
Scale = Pow2MontgomeryModCycleComputationBenchmarks.InputScale.Large
|
||||
};
|
||||
|
||||
// benchmarks.Setup();
|
||||
GpuPrimeWorkLimiter.SetLimit(1);
|
||||
benchmarks.Setup();
|
||||
|
||||
// Console.WriteLine("Press ENTER to run");
|
||||
// Console.ReadLine();
|
||||
|
||||
@@ -458,7 +458,6 @@ internal static class Program
|
||||
|
||||
if (useByDivisor)
|
||||
{
|
||||
threadCount = 1;
|
||||
blockSize = 1;
|
||||
}
|
||||
|
||||
@@ -499,6 +498,7 @@ internal static class Program
|
||||
maxK: residueKMax);
|
||||
if (!useLucas)
|
||||
{
|
||||
Console.WriteLine("Warming up orders");
|
||||
tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
|
||||
@@ -673,12 +673,11 @@ internal static class Program
|
||||
|
||||
if (!useDivisor && !useByDivisor)
|
||||
{
|
||||
Console.WriteLine("Warming up orders...");
|
||||
|
||||
threadCount = Math.Max(1, threadCount);
|
||||
_ = MersenneTesters.Value;
|
||||
if (!useLucas)
|
||||
{
|
||||
Console.WriteLine("Warming up orders...");
|
||||
MersenneTesters.Value!.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
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;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
public class GpuKernelCompilationTests
|
||||
{
|
||||
private static readonly MethodInfo DivisorCycleCacheLoadKernelMethod =
|
||||
typeof(DivisorCycleCache).GetMethod("LoadKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("DivisorCycleCache.LoadKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetBitReverseKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetBitReverseKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetBitReverseKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetMulKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetMulKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetMulKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetStageKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetStageKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetStageKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetScaleKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetScaleKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetScaleKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetStageMontKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetStageMontKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetStageMontKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetStageBarrettKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetStageBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetStageBarrett128Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetScaleBarrettKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetScaleBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetScaleBarrett128Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetSquareBarrettKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetSquareBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetSquareBarrett128Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetToMont64KernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetToMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetToMont64Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetFromMont64KernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetFromMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetFromMont64Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetSquareMont64KernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetSquareMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetSquareMont64Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetScaleMont64KernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetScaleMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetScaleMont64Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetForwardKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetForwardKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetForwardKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetInverseKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetInverseKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetInverseKernel not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorByDivisorGetKernelMethod =
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernel not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorByDivisorGetExponentKernelMethod =
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetKernelByPrimeExponent", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernelByPrimeExponent not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorByDivisorGetRemainderDeltaKernelMethod =
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetRemainderDeltaKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetRemainderDeltaKernel not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorByDivisorGetRemainderScanKernelMethod =
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetRemainderScanKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetRemainderScanKernel not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorTesterGetKernelMethod =
|
||||
typeof(MersenneNumberDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorGpuTester.GetKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetAddSmallKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetAddSmallKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetAddSmallKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetSubSmallKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetSubSmallKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetSubSmallKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetReduceKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetReduceKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetReduceKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetIsZeroKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetIsZeroKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetIsZeroKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetBatchKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetBatchKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetBatchKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetPartialFactorKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPartialFactorKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPartialFactorKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetOrderKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetOrderKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetOrderKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetPow2ModKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetPow2ModWideKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModWideKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModWideKernel not found.");
|
||||
|
||||
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
|
||||
(Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>)Delegate.CreateDelegate(
|
||||
typeof(Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>),
|
||||
typeof(MersenneDivisorCycles).GetMethod("GpuDivisorCycleKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("MersenneDivisorCycles.GpuDivisorCycleKernel not found."));
|
||||
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
||||
(Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>)Delegate.CreateDelegate(
|
||||
typeof(Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>),
|
||||
typeof(PrimeTester).GetMethod("SmallPrimeSieveKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeTester.SmallPrimeSieveKernel not found."));
|
||||
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
|
||||
(Action<Index1D, ArrayView<ulong>, ArrayView<byte>>)Delegate.CreateDelegate(
|
||||
typeof(Action<Index1D, ArrayView<ulong>, ArrayView<byte>>),
|
||||
typeof(PrimeTester).GetMethod("SharesFactorKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeTester.SharesFactorKernel not found."));
|
||||
|
||||
public static IEnumerable<object[]> KernelLoaders()
|
||||
{
|
||||
yield return Loader("DivisorCycleCache.GpuAdvanceDivisorCyclesKernel", CompileDivisorCycleCacheKernel);
|
||||
yield return Loader("NttGpuMath.BitReverseKernel", CompileNttBitReverseKernel);
|
||||
yield return Loader("NttGpuMath.MulKernel", CompileNttMulKernel);
|
||||
yield return Loader("NttGpuMath.StageKernel", CompileNttStageKernel);
|
||||
yield return Loader("NttGpuMath.ScaleKernel", CompileNttScaleKernel);
|
||||
yield return Loader("NttGpuMath.StageMontKernel", CompileNttStageMontKernel);
|
||||
yield return Loader("NttGpuMath.StageBarrett128Kernel", CompileNttStageBarrettKernel);
|
||||
yield return Loader("NttGpuMath.ScaleBarrett128Kernel", CompileNttScaleBarrettKernel);
|
||||
yield return Loader("NttGpuMath.SquareBarrett128Kernel", CompileNttSquareBarrettKernel);
|
||||
yield return Loader("NttGpuMath.ToMont64Kernel", CompileNttToMont64Kernel);
|
||||
yield return Loader("NttGpuMath.FromMont64Kernel", CompileNttFromMont64Kernel);
|
||||
yield return Loader("NttGpuMath.SquareMont64Kernel", CompileNttSquareMont64Kernel);
|
||||
yield return Loader("NttGpuMath.ScaleMont64Kernel", CompileNttScaleMont64Kernel);
|
||||
yield return Loader("NttGpuMath.ForwardKernel", CompileNttForwardKernel);
|
||||
yield return Loader("NttGpuMath.InverseKernel", CompileNttInverseKernel);
|
||||
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.CheckKernel", CompileDivisorByDivisorCheckKernel);
|
||||
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.ComputeMontgomeryExponentKernel", CompileDivisorByDivisorExponentKernel);
|
||||
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.ComputeRemainderDeltasKernel", CompileDivisorByDivisorRemainderDeltaKernel);
|
||||
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.AccumulateRemaindersKernel", CompileDivisorByDivisorRemainderScanKernel);
|
||||
yield return Loader("MersenneNumberDivisorGpuTester.Kernel", CompileDivisorTesterKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.Kernel", CompileLucasLehmerKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.AddSmallKernel", CompileLucasLehmerAddSmallKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.SubtractSmallKernel", CompileLucasLehmerSubSmallKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.ReduceModMersenneKernel", CompileLucasLehmerReduceKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.IsZeroKernel", CompileLucasLehmerIsZeroKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.KernelBatch", CompileLucasLehmerBatchKernel);
|
||||
yield return Loader("PrimeOrderGpuHeuristics.PartialFactorKernel", CompilePrimeOrderPartialFactorKernel);
|
||||
yield return Loader("PrimeOrderGpuHeuristics.CalculateOrderKernel", CompilePrimeOrderKernel);
|
||||
yield return Loader("PrimeOrderGpuHeuristics.Pow2ModKernel", CompilePrimeOrderPow2ModKernel);
|
||||
yield return Loader("PrimeOrderGpuHeuristics.Pow2ModKernelWide", CompilePrimeOrderPow2ModWideKernel);
|
||||
yield return Loader("MersenneDivisorCycles.GpuDivisorCycleKernel", CompileMersenneDivisorCyclesKernel);
|
||||
yield return Loader("PrimeTester.SmallPrimeSieveKernel", CompilePrimeTesterSmallPrimeKernel);
|
||||
yield return Loader("PrimeTester.SharesFactorKernel", CompilePrimeTesterSharesFactorKernel);
|
||||
yield return Loader("GpuKernelPool.KernelLeaseKernels", CompileGpuKernelPoolKernels);
|
||||
}
|
||||
|
||||
private static object[] Loader(string name, Action<Accelerator> loader)
|
||||
{
|
||||
return new object[] { name, loader };
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(KernelLoaders))]
|
||||
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||
{
|
||||
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)
|
||||
{
|
||||
_ = DivisorCycleCacheLoadKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttBitReverseKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetBitReverseKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttMulKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetMulKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttStageKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetStageKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttScaleKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetScaleKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttStageMontKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetStageMontKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttStageBarrettKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetStageBarrettKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttScaleBarrettKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetScaleBarrettKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttSquareBarrettKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetSquareBarrettKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttToMont64Kernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetToMont64KernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttFromMont64Kernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetFromMont64KernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttSquareMont64Kernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetSquareMont64KernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttScaleMont64Kernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetScaleMont64KernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttForwardKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetForwardKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttInverseKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetInverseKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorByDivisorCheckKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
_ = DivisorByDivisorGetKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorByDivisorExponentKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
_ = DivisorByDivisorGetExponentKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorByDivisorRemainderDeltaKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
_ = DivisorByDivisorGetRemainderDeltaKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorByDivisorRemainderScanKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
_ = DivisorByDivisorGetRemainderScanKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorTesterKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
_ = DivisorTesterGetKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerAddSmallKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetAddSmallKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerSubSmallKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetSubSmallKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerReduceKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetReduceKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerIsZeroKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetIsZeroKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerBatchKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetBatchKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompilePrimeOrderPartialFactorKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetPartialFactorKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompilePrimeOrderKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetOrderKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompilePrimeOrderPow2ModKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetPow2ModKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(MersenneDivisorCyclesKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
|
||||
{
|
||||
_ = accelerator;
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: false);
|
||||
try
|
||||
{
|
||||
_ = lease.OrderKernel;
|
||||
_ = lease.IncrementalKernel;
|
||||
_ = lease.Pow2ModKernel;
|
||||
_ = lease.IncrementalOrderKernel;
|
||||
_ = lease.Pow2ModOrderKernel;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
|
||||
var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
_ = orderLease.OrderKernel;
|
||||
_ = orderLease.IncrementalKernel;
|
||||
_ = orderLease.Pow2ModKernel;
|
||||
_ = orderLease.IncrementalOrderKernel;
|
||||
_ = orderLease.Pow2ModOrderKernel;
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderLease.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
@@ -40,7 +40,9 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_success_for_supported_prime()
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, 193UL, out ulong remainder);
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
remainder.Should().Be(2UL);
|
||||
@@ -54,7 +56,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
@@ -73,12 +76,14 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, 193UL, out ulong remainder);
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
|
||||
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, 193UL, out ulong followUpRemainder);
|
||||
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, prime, out ulong followUpRemainder, divisorData);
|
||||
followUp.Should().Be(GpuPow2ModStatus.Success);
|
||||
followUpRemainder.Should().Be(16UL);
|
||||
}
|
||||
@@ -98,7 +103,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
|
||||
ulong[] remainders = new ulong[2];
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
@@ -115,7 +121,9 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
{
|
||||
ulong[] remainders = { 123UL, 456UL };
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, 257UL, remainders);
|
||||
const ulong prime = 257UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
remainders[0].Should().Be(2UL);
|
||||
@@ -133,7 +141,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
ulong[] remainders = { 5UL, 6UL };
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
@@ -156,7 +165,9 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
ulong[] exponents = { 2UL, 16UL };
|
||||
ulong[] remainders = { 9UL, 11UL };
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, 193UL, remainders);
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
@@ -322,16 +333,22 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
|
||||
{
|
||||
ulong[] remainders = new ulong[1];
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
// This will never happen in production code because the caller ensures the remainder span is large enough.
|
||||
// [Fact]
|
||||
// public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
|
||||
// {
|
||||
// ulong[] remainders = new ulong[1];
|
||||
// ulong[] exponents = { 1UL, 2UL };
|
||||
|
||||
Action act = () => PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, 5UL, remainders);
|
||||
// Action act = () =>
|
||||
// {
|
||||
// const ulong prime = 5UL;
|
||||
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
// PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
// };
|
||||
|
||||
act.Should().Throw<ArgumentException>();
|
||||
}
|
||||
// act.Should().Throw<ArgumentException>();
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked()
|
||||
@@ -341,10 +358,10 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(prime, null, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
result.Order.Should().Be(3UL);
|
||||
result.Should().Be(3UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -38,7 +38,8 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
expected.Should().Contain(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor));
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -86,7 +87,8 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor));
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
|
||||
uniqueDivisors.Should().BeEquivalentTo(expected);
|
||||
|
||||
@@ -18,7 +18,8 @@ public class MersenneDivisorCyclesTests
|
||||
[MemberData(nameof(Divisors))]
|
||||
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
|
||||
{
|
||||
MersenneDivisorCycles.CalculateCycleLength(divisor).Should().Be(expected);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData).Should().Be(expected);
|
||||
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberDivisorByDivisorTesterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Run_marks_candidates_divisible_by_seven_or_eleven_as_composite()
|
||||
{
|
||||
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
|
||||
var tester = new FakeTester();
|
||||
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed)>();
|
||||
int compositeMarks = 0;
|
||||
int clearedMarks = 0;
|
||||
|
||||
MersenneNumberDivisorByDivisorTester.Run(
|
||||
candidates,
|
||||
tester,
|
||||
previousResults: null,
|
||||
startPrime: 0UL,
|
||||
markComposite: () => compositeMarks++,
|
||||
clearComposite: () => clearedMarks++,
|
||||
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
|
||||
threadCount: 1);
|
||||
|
||||
tester.ConfiguredMaxPrime.Should().Be(13UL);
|
||||
compositeMarks.Should().Be(2);
|
||||
clearedMarks.Should().Be(2);
|
||||
results.Should().Equal(
|
||||
new[]
|
||||
{
|
||||
(5UL, true, true, true),
|
||||
(7UL, true, true, false),
|
||||
(11UL, true, true, false),
|
||||
(13UL, true, true, true),
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class FakeTester : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
public ulong ConfiguredMaxPrime { get; private set; }
|
||||
|
||||
public int BatchSize { get; set; }
|
||||
|
||||
public ulong DivisorLimit => ConfiguredMaxPrime + 100UL;
|
||||
|
||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
ConfiguredMaxPrime = maxPrime;
|
||||
}
|
||||
|
||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||
{
|
||||
return DivisorLimit;
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
||||
}
|
||||
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||
{
|
||||
return new DummySession();
|
||||
}
|
||||
|
||||
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
for (int i = 0; i < primes.Length; i++)
|
||||
{
|
||||
allowedMaxValues[i] = 100UL;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,14 +35,50 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL);
|
||||
session.CheckDivisor(31UL, cycle31, primes, hits);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(61UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
@@ -11,15 +12,18 @@ namespace PerfectNumbers.Core.Tests;
|
||||
public class MersenneNumberDivisorGpuTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(3UL, 7UL, true)]
|
||||
[InlineData(11UL, 23UL, true)]
|
||||
[InlineData(7UL, 35UL, false)]
|
||||
[InlineData(13UL, 23UL, false)]
|
||||
[InlineData(33UL, 7UL, true)]
|
||||
[InlineData(35UL, 31UL, true)]
|
||||
[InlineData(37UL, 223UL, true)]
|
||||
[InlineData(33UL, 13UL, false)]
|
||||
[InlineData(35UL, 73UL, false)]
|
||||
[InlineData(37UL, 227UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
tester.IsDivisible(exponent, divisor).Should().Be(expected);
|
||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||
tester.IsDivisible(exponent, in divisorValue).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -28,8 +32,10 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
UInt128 divisor = (UInt128.One << 65) - UInt128.One;
|
||||
tester.IsDivisible(65UL, divisor).Should().BeTrue();
|
||||
tester.IsDivisible(65UL, divisor + 2).Should().BeFalse();
|
||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||
tester.IsDivisible(65UL, in divisorValue).Should().BeTrue();
|
||||
divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate.
|
||||
tester.IsDivisible(65UL, in divisorValue).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,7 +47,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
|
||||
.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||
|
||||
tester.IsPrime(11UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
tester.IsPrime(31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeFalse();
|
||||
}
|
||||
|
||||
@@ -50,7 +56,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
tester.IsPrime(3UL, 7UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
tester.IsPrime(35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -59,7 +65,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void IsPrime_accepts_large_search_limits()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
tester.IsPrime(3UL, 7UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
|
||||
tester.IsPrime(35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -68,15 +74,15 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(11UL);
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
tester.IsPrime(5UL, out bool exhausted).Should().BeTrue();
|
||||
tester.IsPrime(31UL, out bool exhausted).Should().BeTrue();
|
||||
exhausted.Should().BeTrue();
|
||||
|
||||
tester.IsPrime(7UL, out exhausted).Should().BeTrue();
|
||||
tester.IsPrime(37UL, out exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
|
||||
tester.IsPrime(11UL, out exhausted).Should().BeFalse();
|
||||
tester.IsPrime(43UL, out exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -85,7 +91,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void ByDivisor_tester_requires_configuration()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
Action act = () => tester.IsPrime(5UL, out _);
|
||||
Action act = () => tester.IsPrime(31UL, out _);
|
||||
act.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
@@ -94,16 +100,30 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void ByDivisor_tester_respects_filter_based_limit()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(5UL);
|
||||
tester.ConfigureFromMaxPrime(41UL);
|
||||
|
||||
tester.IsPrime(5UL, out _).Should().BeTrue();
|
||||
tester.IsPrime(7UL, out bool exhausted).Should().BeTrue();
|
||||
tester.IsPrime(31UL, out _).Should().BeTrue();
|
||||
tester.IsPrime(37UL, out bool exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
|
||||
tester.IsPrime(11UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(71UL, false)]
|
||||
[InlineData(89UL, true)]
|
||||
[InlineData(127UL, true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(exponent);
|
||||
|
||||
tester.IsPrime(exponent, out bool exhausted).Should().Be(expectedPrime);
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor()
|
||||
@@ -113,9 +133,9 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
GpuBatchSize = 8,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(13UL);
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
tester.IsPrime(11UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -124,20 +144,20 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void ByDivisor_session_checks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(13UL);
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
|
||||
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL);
|
||||
session.CheckDivisor(31UL, cycle31, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -149,16 +169,122 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
GpuBatchSize = 2,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(17UL);
|
||||
tester.ConfigureFromMaxPrime(47UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL, 17UL };
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0, 0 });
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
GpuBatchSize = 5,
|
||||
};
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession)
|
||||
.GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!;
|
||||
Span<ulong> residues = hostBuffer.AsSpan(0, exponents.Length);
|
||||
byte[] computedHits = new byte[exponents.Length];
|
||||
for (int i = 0; i < residues.Length; i++)
|
||||
{
|
||||
computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
GpuBatchSize = 5,
|
||||
};
|
||||
tester.ConfigureFromMaxPrime(61UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession)
|
||||
.GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!;
|
||||
Span<ulong> residues = hostBuffer.AsSpan(0, exponents.Length);
|
||||
byte[] computedHits = new byte[exponents.Length];
|
||||
for (int i = 0; i < residues.Length; i++)
|
||||
{
|
||||
computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
|
||||
{
|
||||
var cycles = MersenneDivisorCycles.Shared;
|
||||
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
|
||||
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
|
||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||
|
||||
try
|
||||
{
|
||||
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
|
||||
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(191UL);
|
||||
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(191UL, divisorData);
|
||||
|
||||
tableField.SetValue(cycles, patchedTable);
|
||||
smallCyclesField.SetValue(cycles, patchedSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(19UL);
|
||||
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester)
|
||||
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
|
||||
.SetValue(tester, 200UL);
|
||||
|
||||
tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
tableField.SetValue(cycles, originalTable);
|
||||
smallCyclesField.SetValue(cycles, originalSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -170,16 +296,16 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
BatchSize = 2,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(17UL);
|
||||
tester.ConfigureFromMaxPrime(47UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL, 17UL };
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0, 0 });
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ public class MersenneNumberIncrementalGpuTesterTests
|
||||
{
|
||||
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
|
||||
|
||||
[Theory]
|
||||
[Theory(Skip = "Disabled until the GPU incremental cycle heuristics are ported.")]
|
||||
[InlineData(GpuKernelType.Pow2Mod, false)]
|
||||
[InlineData(GpuKernelType.Incremental, false)]
|
||||
[InlineData(GpuKernelType.Pow2Mod, true)]
|
||||
@@ -20,7 +20,8 @@ public class MersenneNumberIncrementalGpuTesterTests
|
||||
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 11UL, 1UL, expectedPrime: false);
|
||||
// Mirrored by the divisor GPU coverage to ensure the failing cases stay validated.
|
||||
RunCase(tester, 37UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,13 +45,14 @@ public class PrimeOrderCalculatorTests
|
||||
{
|
||||
ulong expected = ComputeOrderByDoubling(prime);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||
ulong result = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
result.Order.Should().Be(expected);
|
||||
result.Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -61,19 +62,21 @@ public class PrimeOrderCalculatorTests
|
||||
{
|
||||
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
||||
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
||||
ulong strict = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault);
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
heuristic.Order.Should().Be(strict.Order);
|
||||
heuristic.Should().Be(strict);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -82,13 +85,14 @@ public class PrimeOrderCalculatorTests
|
||||
[InlineData(3UL, 2UL)]
|
||||
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
||||
{
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||
ulong result = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
result.Order.Should().Be(expectedOrder);
|
||||
result.Should().Be(expectedOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -101,13 +105,14 @@ public class PrimeOrderCalculatorTests
|
||||
maxPowChecks: 8,
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||
ulong result = PrimeOrderCalculator.Calculate(
|
||||
prime: 13UL,
|
||||
previousOrder: null,
|
||||
config);
|
||||
MontgomeryDivisorData.FromModulus(13UL),
|
||||
config,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
||||
result.Order.Should().Be(ComputeOrderByDoubling(13UL));
|
||||
result.Should().Be(ComputeOrderByDoubling(13UL));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -120,13 +125,15 @@ public class PrimeOrderCalculatorTests
|
||||
maxPowChecks: 1,
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
||||
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
heuristicConfig);
|
||||
divisorData,
|
||||
heuristicConfig,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
||||
heuristic.Order.Should().Be(ComputeOrderByDoubling(239UL));
|
||||
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
||||
|
||||
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||
smallFactorLimit: 2,
|
||||
@@ -134,13 +141,14 @@ public class PrimeOrderCalculatorTests
|
||||
maxPowChecks: 1,
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
||||
ulong strict = PrimeOrderCalculator.Calculate(
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
strictConfig);
|
||||
divisorData,
|
||||
strictConfig,
|
||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
strict.Order.Should().Be(heuristic.Order);
|
||||
strict.Should().Be(heuristic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -149,13 +157,12 @@ public class PrimeOrderCalculatorTests
|
||||
{
|
||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResultWide result = PrimeOrderCalculator.Calculate(
|
||||
UInt128 result = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
result.Order.Should().Be((UInt128)1229782938247309446UL);
|
||||
result.Should().Be((UInt128)1229782938247309446UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
internal static class DeterministicRandom
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static ulong s_threadState;
|
||||
|
||||
private static long s_seedCounter;
|
||||
|
||||
public static ulong NextUInt64()
|
||||
{
|
||||
ulong state = s_threadState;
|
||||
if (state == 0UL)
|
||||
{
|
||||
state = InitializeThreadState();
|
||||
}
|
||||
|
||||
state ^= state >> 12;
|
||||
state ^= state << 25;
|
||||
state ^= state >> 27;
|
||||
s_threadState = state;
|
||||
return state * 2685821657736338717UL;
|
||||
}
|
||||
|
||||
public static UInt128 NextUInt128()
|
||||
{
|
||||
UInt128 high = (UInt128)NextUInt64();
|
||||
UInt128 low = (UInt128)NextUInt64();
|
||||
return (high << 64) | low;
|
||||
}
|
||||
|
||||
private static ulong InitializeThreadState()
|
||||
{
|
||||
ulong seed = (ulong)Interlocked.Increment(ref s_seedCounter);
|
||||
seed += 0x9E3779B97F4A7C15UL;
|
||||
seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL;
|
||||
seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL;
|
||||
seed ^= seed >> 31;
|
||||
if (seed == 0UL)
|
||||
{
|
||||
seed = 0x9E3779B97F4A7C15UL;
|
||||
}
|
||||
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
@@ -109,14 +109,16 @@ public sealed class DivisorCycleCache
|
||||
ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
int targetIndex = indices[i];
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisors[targetIndex]);
|
||||
ulong divisor = divisors[targetIndex];
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
internal struct ExponentRemainderStepper
|
||||
internal struct ExponentRemainderStepper(in MontgomeryDivisorData divisor)
|
||||
{
|
||||
private readonly MontgomeryDivisorData _divisor;
|
||||
private readonly ulong _modulus;
|
||||
private readonly ulong _nPrime;
|
||||
private readonly ulong _montgomeryOne;
|
||||
private ulong _previousExponent;
|
||||
private ulong _currentMontgomery;
|
||||
private bool _hasState;
|
||||
private readonly MontgomeryDivisorData _divisor = divisor;
|
||||
private readonly ulong _modulus = divisor.Modulus;
|
||||
private readonly ulong _nPrime = divisor.NPrime;
|
||||
private readonly ulong _montgomeryOne = divisor.MontgomeryOne;
|
||||
private ulong _previousExponent = 0UL;
|
||||
private ulong _currentMontgomery = divisor.MontgomeryOne;
|
||||
private bool _hasState = false;
|
||||
|
||||
public ExponentRemainderStepper(in MontgomeryDivisorData divisor)
|
||||
{
|
||||
_divisor = divisor;
|
||||
_modulus = divisor.Modulus;
|
||||
_nPrime = divisor.NPrime;
|
||||
_montgomeryOne = divisor.MontgomeryOne;
|
||||
_previousExponent = 0UL;
|
||||
_currentMontgomery = divisor.MontgomeryOne;
|
||||
_hasState = false;
|
||||
}
|
||||
public readonly bool IsValidModulus => _modulus > 1UL && (_modulus & 1UL) != 0UL;
|
||||
|
||||
public bool IsValidModulus => _modulus > 1UL && (_modulus & 1UL) != 0UL;
|
||||
public readonly bool HasState => _hasState;
|
||||
|
||||
public bool HasState => _hasState;
|
||||
|
||||
public ulong PreviousExponent => _previousExponent;
|
||||
public readonly ulong PreviousExponent => _previousExponent;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
@@ -47,7 +36,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.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
_previousExponent = exponent;
|
||||
_hasState = true;
|
||||
return ReduceCurrent();
|
||||
@@ -59,7 +48,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.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||
_previousExponent = exponent;
|
||||
return ReduceCurrent();
|
||||
@@ -77,7 +66,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.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
_previousExponent = exponent;
|
||||
_hasState = true;
|
||||
return _currentMontgomery == _montgomeryOne;
|
||||
@@ -88,7 +77,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.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||
_previousExponent = exponent;
|
||||
return _currentMontgomery == _montgomeryOne;
|
||||
@@ -123,7 +112,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.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
_previousExponent = exponent;
|
||||
_hasState = true;
|
||||
isUnity = _currentMontgomery == _montgomeryOne;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace PerfectNumbers.Core
|
||||
{
|
||||
public readonly struct FactorEntry(ulong value, int exponent)
|
||||
{
|
||||
public readonly ulong Value = value;
|
||||
public readonly int Exponent = exponent;
|
||||
}
|
||||
}
|
||||
@@ -25,16 +25,16 @@ public readonly struct ResidueAutomatonArgs
|
||||
|
||||
public sealed class KernelContainer
|
||||
{
|
||||
// Serializes first-time initialization of kernels/buffers per accelerator.
|
||||
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
|
||||
// Serializes first-time initialization of kernels/buffers per accelerator.
|
||||
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
|
||||
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
|
||||
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
|
||||
|
||||
@@ -45,22 +45,22 @@ public sealed class KernelContainer
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
|
||||
|
||||
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
|
||||
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
|
||||
{
|
||||
var current = Volatile.Read(ref slot);
|
||||
if (current is not null)
|
||||
{
|
||||
var current = Volatile.Read(ref slot);
|
||||
if (current is not null)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
current = Volatile.Read(ref slot);
|
||||
if (current is null)
|
||||
{
|
||||
current = factory();
|
||||
Volatile.Write(ref slot, current);
|
||||
}
|
||||
return current;
|
||||
return current;
|
||||
}
|
||||
|
||||
current = Volatile.Read(ref slot);
|
||||
if (current is null)
|
||||
{
|
||||
current = factory();
|
||||
Volatile.Write(ref slot, current);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResiduePrimeViews(
|
||||
@@ -77,122 +77,120 @@ public readonly struct ResiduePrimeViews(
|
||||
|
||||
public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels)
|
||||
{
|
||||
private bool disposedValue;
|
||||
private IDisposable? _limiter = limiter;
|
||||
private GpuContextLease? _gpu = gpu;
|
||||
private KernelContainer _kernels = kernels;
|
||||
private AcceleratorStream? _stream;
|
||||
private object? _executionLock = gpu.ExecutionLock;
|
||||
private bool disposedValue;
|
||||
private IDisposable? _limiter = limiter;
|
||||
private GpuContextLease? _gpu = gpu;
|
||||
private KernelContainer _kernels = kernels;
|
||||
private AcceleratorStream? _stream;
|
||||
private object? _executionLock = gpu.ExecutionLock;
|
||||
|
||||
public readonly Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released");
|
||||
public readonly Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released");
|
||||
|
||||
public AcceleratorStream Stream
|
||||
public AcceleratorStream Stream
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
if (_stream is { } stream)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
// It feels that we can combine these into 1 return statement, but that increases the max stack frame size to 3 from 2
|
||||
stream = Accelerator.CreateStream();
|
||||
_stream = stream;
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ExecutionScope EnterExecutionScope() => new ExecutionScope(_executionLock);
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator; // avoid capturing 'this' in lambda
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Order, () =>
|
||||
{
|
||||
if (_stream is { } stream)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
stream = Accelerator.CreateStream();
|
||||
_stream = stream;
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ExecutionScope EnterExecutionScope()
|
||||
{
|
||||
return new ExecutionScope(_executionLock);
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator; // avoid capturing 'this' in lambda
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Order, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () =>
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Incremental, () =>
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Incremental, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () =>
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () =>
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
||||
// (some already accept it). Consider a compact type (byte/ushort) for memory footprint.
|
||||
// TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
||||
// (some already accept it). Consider a compact type (byte/ushort) for memory footprint.
|
||||
|
||||
private static void IncrementalKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong divMul,
|
||||
ulong q0m10, ulong q0m8, ulong q0m3, ulong q0m5, ArrayView<ulong> orders,
|
||||
@@ -206,21 +204,21 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
// residue automaton
|
||||
ulong step10 = (exponent.Mod10() << 1).Mod10();
|
||||
ulong r10 = q0m10 + (step10 * idx).Mod10();
|
||||
r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL; // skip q ≡ 5 (mod 10); other residues handled by r8/r3/r5
|
||||
if (shouldCheck)
|
||||
{
|
||||
r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL; // skip q ≡ 5 (mod 10); other residues handled by r8/r3/r5
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong r8 = (q0m8 + ((step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Swap these `%` filters to the shared Mod helpers so the residue automaton matches the benchmarked
|
||||
// bitmask-based implementation for GPU workloads.
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
// TODO: Swap these `%` filters to the shared Mod helpers so the residue automaton matches the benchmarked
|
||||
// bitmask-based implementation for GPU workloads.
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong r3 = q0m3 + (step3 * idxMod3); r3 -= (r3 >= 3UL) ? 3UL : 0UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
@@ -236,19 +234,21 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldCheck)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Is this expected that the value of kStart is modified?
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP;
|
||||
q.Mul64(k);
|
||||
q.Add(GpuUInt128.One);
|
||||
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
|
||||
{
|
||||
@@ -260,7 +260,9 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: q will be lowered by GpuUInt128.One here. Is this expected? Maybe that's why we have issues with the incremental kernel? Review other places where we use GpuUInt128 operators.
|
||||
GpuUInt128 phi = q - GpuUInt128.One;
|
||||
// TODO: Do we have the check here because we don't support high values or this is expected result?
|
||||
if (phi.High != 0UL)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -270,13 +272,13 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong phi64 = phi.Low;
|
||||
// TODO: Replace these Pow2Mod calls with the ProcessEightBitWindows helper when the shared windowed
|
||||
// scalar implementation lands; benchmarks showed the windowed kernel trimming per-divisor runtime by ~2.4×.
|
||||
if (GpuUInt128.Pow2Mod(phi64, q) != GpuUInt128.One)
|
||||
if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -284,7 +286,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
|
||||
ulong div = FastDiv64Gpu(phi64, exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -297,6 +299,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
private static void OrderKernelScan(Index1D index, ulong exponent, ulong divMul, ArrayView<GpuUInt128> qs, ArrayView<ulong> orders)
|
||||
{
|
||||
GpuUInt128 q = qs[index];
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
GpuUInt128 phi = q - GpuUInt128.One;
|
||||
if (phi.High != 0UL)
|
||||
{
|
||||
@@ -307,14 +310,14 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong phi64 = phi.Low;
|
||||
// TODO: Once the ProcessEightBitWindows helper is available, switch this order kernel to that faster
|
||||
// Pow2Mod variant so cycle checks inherit the same gains observed in GpuPow2ModBenchmarks.
|
||||
GpuUInt128 pow = GpuUInt128.Pow2Mod(phi64, q);
|
||||
GpuUInt128 pow = GpuUInt128.Pow2Mod(phi64, in readOnlyQ);
|
||||
if (pow != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -322,7 +325,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
|
||||
ulong div = FastDiv64Gpu(phi64, exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -332,29 +335,29 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
orders[index] = exponent;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_stream?.Dispose();
|
||||
_stream = null;
|
||||
if (disposing)
|
||||
{
|
||||
_stream?.Dispose();
|
||||
_stream = null;
|
||||
|
||||
_gpu?.Dispose();
|
||||
_gpu = null;
|
||||
_gpu?.Dispose();
|
||||
_gpu = null;
|
||||
|
||||
_executionLock = null;
|
||||
_executionLock = null;
|
||||
|
||||
_limiter?.Dispose();
|
||||
_limiter = null;
|
||||
}
|
||||
_limiter?.Dispose();
|
||||
_limiter = null;
|
||||
}
|
||||
|
||||
// free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
// free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Pow2ModKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _,
|
||||
ResidueAutomatonArgs ra, ArrayView<ulong> orders,
|
||||
@@ -404,13 +407,16 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
// TODO: kStart is modified after this. Is this expected?
|
||||
kStart.Add(idx);
|
||||
GpuUInt128 q = twoP;
|
||||
q.Mul64(k);
|
||||
q.Mul64(kStart);
|
||||
q.Add(GpuUInt128.One);
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
|
||||
{
|
||||
ulong cycle = smallCycles[(int)q.Low];
|
||||
// cycle should always be initialized if we're within array limit in production code
|
||||
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -419,7 +425,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
// TODO: Swap Pow2Minus1Mod for the eight-bit window helper once the scalar version switches;
|
||||
// benchmarks show the windowed variant cuts large-divisor scans from ~51 µs to ~21 µs.
|
||||
if (GpuUInt128.Pow2Minus1Mod(exponent, q) != GpuUInt128.Zero)
|
||||
if (GpuUInt128.Pow2Minus1Mod(exponent, in readOnlyQ) != GpuUInt128.Zero)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
@@ -517,45 +523,46 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong idxMod3 = idx % 3UL;
|
||||
ulong idxMod5 = idx % 5UL;
|
||||
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
|
||||
if (r3 >= 6UL) r3 -= 6UL;
|
||||
if (r3 >= 3UL) r3 -= 3UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
|
||||
if (r5 >= 15UL) r5 -= 15UL;
|
||||
if (r5 >= 10UL) r5 -= 10UL;
|
||||
if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
|
||||
if (r3 >= 6UL) r3 -= 6UL;
|
||||
if (r3 >= 3UL) r3 -= 3UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
|
||||
if (r5 >= 15UL) r5 -= 15UL;
|
||||
if (r5 >= 10UL) r5 -= 10UL;
|
||||
if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP;
|
||||
q.Mul64(k);
|
||||
q.Add(GpuUInt128.One);
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
|
||||
@@ -575,19 +582,19 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong phi64 = phi.Low;
|
||||
// TODO: Upgrade this pow2mod order kernel to the ProcessEightBitWindows helper once available so GPU residue
|
||||
// scans avoid the single-bit ladder that benchmarks found to be 2.3× slower on large exponents.
|
||||
if (GpuUInt128.Pow2Mod(phi64, q) != GpuUInt128.One)
|
||||
if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong div = FastDiv64Gpu(phi64, exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
@@ -603,45 +610,46 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong idxMod3 = idx % 3UL;
|
||||
ulong idxMod5 = idx % 5UL;
|
||||
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
|
||||
if (r3 >= 6UL) r3 -= 6UL;
|
||||
if (r3 >= 3UL) r3 -= 3UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
|
||||
if (r5 >= 15UL) r5 -= 15UL;
|
||||
if (r5 >= 10UL) r5 -= 10UL;
|
||||
if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
|
||||
if (r3 >= 6UL) r3 -= 6UL;
|
||||
if (r3 >= 3UL) r3 -= 3UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
|
||||
if (r5 >= 15UL) r5 -= 15UL;
|
||||
if (r5 >= 10UL) r5 -= 10UL;
|
||||
if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP;
|
||||
q.Mul64(k);
|
||||
q.Add(GpuUInt128.One);
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
|
||||
{
|
||||
@@ -653,7 +661,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
// TODO: Replace this Pow2Mod check with the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
|
||||
// residue order scans will then benefit from the same 2× speedup the benchmarked windowed kernel delivered.
|
||||
if (GpuUInt128.Pow2Mod(exponent, q) != GpuUInt128.One)
|
||||
if (GpuUInt128.Pow2Mod(exponent, in readOnlyQ) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -661,35 +669,35 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
Atomic.Or(ref found[0], 1);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
// Enabled this when the struct is converted to a class
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public readonly struct ExecutionScope : IDisposable
|
||||
{
|
||||
private readonly object? _lock;
|
||||
|
||||
public ExecutionScope(object? sync)
|
||||
{
|
||||
_lock = sync;
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Enter(_lock);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
// Enabled this when the struct is converted to a class
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public readonly struct ExecutionScope : IDisposable
|
||||
{
|
||||
private readonly object? _lock;
|
||||
|
||||
public ExecutionScope(object? sync)
|
||||
{
|
||||
_lock = sync;
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Enter(_lock);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Exit(_lock);
|
||||
}
|
||||
}
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Exit(_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GpuKernelPool
|
||||
@@ -771,19 +779,19 @@ public class GpuKernelPool
|
||||
}
|
||||
}
|
||||
|
||||
public static GpuKernelLease GetKernel(bool useGpuOrder)
|
||||
{
|
||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
var gpu = RentPreferred(preferCpu: !useGpuOrder);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernels = GetKernels(accelerator);
|
||||
return new(limiter, gpu, kernels);
|
||||
}
|
||||
public static GpuKernelLease GetKernel(bool useGpuOrder)
|
||||
{
|
||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
var gpu = RentPreferred(preferCpu: !useGpuOrder);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernels = GetKernels(accelerator);
|
||||
return new(limiter, gpu, kernels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a GPU action with an acquired accelerator and stream.
|
||||
/// </summary>
|
||||
/// <param name="action">Action to run with (Accelerator, Stream).</param>
|
||||
/// <summary>
|
||||
/// Runs a GPU action with an acquired accelerator and stream.
|
||||
/// </summary>
|
||||
/// <param name="action">Action to run with (Accelerator, Stream).</param>
|
||||
public static void Run(Action<Accelerator, AcceleratorStream> action)
|
||||
{
|
||||
var lease = GetKernel(useGpuOrder: true);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU.Algorithms;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
{
|
||||
public ulong High;
|
||||
|
||||
public ulong Low;
|
||||
|
||||
private const int NativeModuloChunkBits = 8;
|
||||
@@ -20,6 +20,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSizeBits - 1);
|
||||
private const int Pow2WindowMaxExponent = (1 << Pow2WindowSizeBits) - 1;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetScalarBitLength(ulong value) => value == 0UL ? 0 : 64 - XMath.LeadingZeroCount(value);
|
||||
|
||||
public static readonly GpuUInt128 Zero = new();
|
||||
public static readonly GpuUInt128 One = new(1UL);
|
||||
|
||||
@@ -57,45 +60,52 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int GetBitLength()
|
||||
{
|
||||
if (High != 0UL)
|
||||
ulong temp = High;
|
||||
if (temp != 0UL)
|
||||
{
|
||||
return 64 + (64 - BitOperations.LeadingZeroCount(High));
|
||||
return 64 + (64 - BitOperations.LeadingZeroCount(temp));
|
||||
}
|
||||
|
||||
ulong low = Low;
|
||||
if (low == 0UL)
|
||||
temp = Low;
|
||||
if (temp == 0UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 64 - BitOperations.LeadingZeroCount(low);
|
||||
return 64 - BitOperations.LeadingZeroCount(temp);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetBitLength(GpuUInt128 value) => value.GetBitLength();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator GpuUInt128(UInt128 value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator GpuUInt128(ulong value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator GpuUInt128(ReadOnlyGpuUInt128 value) => new(value.High, value.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator UInt128(GpuUInt128 value) =>
|
||||
((UInt128)value.High << 64) | value.Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ReadOnlyGpuUInt128 AsReadOnly() => new(this);
|
||||
|
||||
// Operators must return fresh instances so callers never rely on accidental in-place mutation. Prefer the mutating helpers when state updates are required.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator +(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
left.Add(right);
|
||||
return left;
|
||||
GpuUInt128 result = new(left.High, left.Low);
|
||||
result.Add(right);
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator -(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
left.Sub(right);
|
||||
return left;
|
||||
GpuUInt128 result = new(left.High, left.Low);
|
||||
result.Sub(right);
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -106,61 +116,63 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
public static GpuUInt128 operator *(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
Multiply(left, right, out var high, out var low);
|
||||
left.High = high;
|
||||
left.Low = low;
|
||||
return left;
|
||||
return new GpuUInt128(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator <<(GpuUInt128 value, int shift)
|
||||
{
|
||||
shift &= 127;
|
||||
GpuUInt128 result = new(value.High, value.Low);
|
||||
if (shift == 0)
|
||||
{
|
||||
return value;
|
||||
return result;
|
||||
}
|
||||
|
||||
ulong high = value.High;
|
||||
ulong low = value.Low;
|
||||
ulong high = result.High;
|
||||
ulong low = result.Low;
|
||||
|
||||
int shiftedValue;
|
||||
if (shift >= 64)
|
||||
{
|
||||
int longShift = shift - 64;
|
||||
value.High = low << longShift;
|
||||
value.Low = 0UL;
|
||||
return value;
|
||||
shiftedValue = shift - 64;
|
||||
result.High = low << shiftedValue;
|
||||
result.Low = 0UL;
|
||||
return result;
|
||||
}
|
||||
|
||||
int inverseShift = 64 - shift;
|
||||
value.High = (high << shift) | (low >> inverseShift);
|
||||
value.Low = low << shift;
|
||||
return value;
|
||||
shiftedValue = 64 - shift;
|
||||
result.High = (high << shift) | (low >> shiftedValue);
|
||||
result.Low = low << shift;
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator >>(GpuUInt128 value, int shift)
|
||||
{
|
||||
shift &= 127;
|
||||
GpuUInt128 result = new(value.High, value.Low);
|
||||
if (shift == 0)
|
||||
{
|
||||
return value;
|
||||
return result;
|
||||
}
|
||||
|
||||
ulong high = value.High;
|
||||
ulong low = value.Low;
|
||||
ulong high = result.High;
|
||||
ulong low = result.Low;
|
||||
|
||||
int shiftedValue;
|
||||
if (shift >= 64)
|
||||
{
|
||||
int longShift = shift - 64;
|
||||
value.Low = longShift == 0 ? high : high >> longShift;
|
||||
value.High = 0UL;
|
||||
return value;
|
||||
shiftedValue = shift - 64;
|
||||
result.Low = shiftedValue == 0 ? high : high >> shiftedValue;
|
||||
result.High = 0UL;
|
||||
return result;
|
||||
}
|
||||
|
||||
int inverseShift = 64 - shift;
|
||||
value.Low = (low >> shift) | (high << inverseShift);
|
||||
value.High = high >> shift;
|
||||
return value;
|
||||
shiftedValue = 64 - shift;
|
||||
result.Low = (low >> shift) | (high << shiftedValue);
|
||||
result.High = high >> shift;
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -184,8 +196,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator ++(GpuUInt128 value)
|
||||
{
|
||||
value.Add(1UL);
|
||||
return value;
|
||||
GpuUInt128 result = new(value.High, value.Low);
|
||||
result.Add(1UL);
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -214,6 +227,33 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int CompareTo(in ReadOnlyGpuUInt128 other)
|
||||
{
|
||||
if (High < other.High)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (High > other.High)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
ulong otherLow = other.Low;
|
||||
if (Low < otherLow)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Low > otherLow)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Equals(GpuUInt128 other) => High == other.High && Low == other.Low;
|
||||
|
||||
@@ -224,41 +264,70 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
public override readonly int GetHashCode() => HashCode.Combine(High, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(GpuUInt128 other)
|
||||
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddInternal(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
ulong originalLow = Low;
|
||||
ulong low = originalLow + other.Low;
|
||||
ulong carry = low < originalLow ? 1UL : 0UL;
|
||||
High = High + other.High + carry;
|
||||
// We're reusing originalLow for carry to avoid an extra local.
|
||||
ulong low = originalLow + otherLow;
|
||||
originalLow = low < originalLow ? 1UL : 0UL;
|
||||
Low = low;
|
||||
// We're reusing low for the final sum to avoid an extra local and to keep max stack frame at 3 instead of 4 with one statement.
|
||||
low = High + otherHigh;
|
||||
High = low + originalLow;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(ulong value)
|
||||
{
|
||||
ulong low = Low + value;
|
||||
High += (low < Low ? 1UL : 0UL);
|
||||
High += low < Low ? 1UL : 0UL;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddModInternal(ulong valueHigh, ulong valueLow, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
AddMod(value, modulus.Low);
|
||||
AddMod(valueLow, modulus.Low);
|
||||
return;
|
||||
}
|
||||
|
||||
Add(value);
|
||||
if (CompareTo(modulus) >= 0)
|
||||
AddInternal(valueHigh, valueLow);
|
||||
if (CompareTo(in modulus) >= 0)
|
||||
{
|
||||
Sub(modulus);
|
||||
Sub(in modulus);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(ulong value, GpuUInt128 modulus)
|
||||
public void AddMod(ulong value, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -267,19 +336,20 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
Add(value);
|
||||
if (CompareTo(modulus) >= 0)
|
||||
if (CompareTo(in modulus) >= 0)
|
||||
{
|
||||
Sub(modulus);
|
||||
Sub(in modulus);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 Pow2Minus1Mod(ulong exponent, GpuUInt128 modulus)
|
||||
public static GpuUInt128 Pow2Minus1Mod(ulong exponent, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.IsZero)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
// This should never happen in production code
|
||||
// if (modulus.IsZero)
|
||||
// {
|
||||
// return Zero;
|
||||
// }
|
||||
|
||||
GpuUInt128 pow = Pow2Mod(exponent, modulus);
|
||||
if (pow.IsZero)
|
||||
@@ -292,7 +362,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Pow2Minus1ModBatch(GpuUInt128 modulus, ReadOnlySpan<ulong> exponents, Span<GpuUInt128> results)
|
||||
public static void Pow2Minus1ModBatch(in ReadOnlyGpuUInt128 modulus, ReadOnlySpan<ulong> exponents, Span<GpuUInt128> results)
|
||||
{
|
||||
int count = exponents.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
@@ -302,7 +372,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(GpuUInt128 value, ulong modulus)
|
||||
public void AddMod(in ReadOnlyGpuUInt128 value, ulong modulus)
|
||||
{
|
||||
// TODO: Pre-reduce the operands via the Montgomery ladder used in MulMod64Benchmarks so the GPU
|
||||
// compatible shim stops paying for `%` on every call; the InlineUInt128 helper ran 6–82× faster on
|
||||
@@ -319,6 +389,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
Low = sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(ulong value, ulong modulus)
|
||||
{
|
||||
@@ -338,57 +411,74 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Sub(GpuUInt128 other)
|
||||
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Sub(GpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SubInternal(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
ulong borrow = Low < other.Low ? 1UL : 0UL;
|
||||
High = High - other.High - borrow;
|
||||
Low -= other.Low;
|
||||
ulong borrow = Low < otherLow ? 1UL : 0UL;
|
||||
High = High - otherHigh - borrow;
|
||||
Low -= otherLow;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong MulHigh(ulong x, ulong y)
|
||||
{
|
||||
// TODO: Review if we can reuse any other existing variables to reduce registry pressure.
|
||||
ulong xLow = (uint)x;
|
||||
ulong yLow = (uint)y;
|
||||
ulong lowProduct = xLow * yLow;
|
||||
|
||||
ulong yHigh = y >> 32;
|
||||
ulong cross = xLow * yHigh;
|
||||
// We're reusing xLow for the cross product to avoid an extra local.
|
||||
xLow *= yHigh;
|
||||
|
||||
ulong xHigh = x >> 32;
|
||||
ulong result = xHigh * yHigh;
|
||||
ulong temp = xHigh * yLow;
|
||||
// We're reusing yHigh for the result to void an extra local.
|
||||
yHigh *= xHigh;
|
||||
// We're reusing yLow for the other cross product to avoid an extra local.
|
||||
yLow *= xHigh;
|
||||
|
||||
// Keeping the wide partial sum in a dedicated local prevents the JIT from
|
||||
// materialising it on the stack before the final carry propagation. The
|
||||
// additional store looks redundant in C#, but it shortens the generated
|
||||
// instruction sequence by avoiding an extra temporary and results in a
|
||||
// measurable throughput win in the MulHigh benchmarks.
|
||||
result += cross >> 32;
|
||||
result += temp >> 32;
|
||||
yHigh += (xLow >> 32);
|
||||
// We've more instructions with this but the max method stack frame is 3 instead of 4.
|
||||
yHigh += (yLow >> 32);
|
||||
|
||||
lowProduct >>= 32;
|
||||
lowProduct += (uint)cross;
|
||||
lowProduct += (uint)temp;
|
||||
result += lowProduct >> 32;
|
||||
return result;
|
||||
lowProduct += (uint)xLow + (uint)yLow;
|
||||
yHigh += lowProduct >> 32;
|
||||
return yHigh;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Mul64(GpuUInt128 other)
|
||||
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Mul64(GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
// Multiply this.Low (assumed 64-bit value) by full 128-bit other
|
||||
ulong operand = Low;
|
||||
Low = operand * other.Low;
|
||||
ulong highProduct = operand * other.High;
|
||||
High = highProduct + MulHigh(operand, other.Low);
|
||||
Low = operand * otherLow;
|
||||
ulong highProduct = operand * otherHigh;
|
||||
High = highProduct + MulHigh(operand, otherLow);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void MulMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
||||
// Kept by-value to coexist with the ref readonly overload below while still signaling read-only intent.
|
||||
public void MulMod(ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
GpuUInt128 multiplicand = this;
|
||||
GpuUInt128 multiplier = value;
|
||||
GpuUInt128 multiplier = value.ToMutable();
|
||||
|
||||
High = 0UL;
|
||||
Low = 0UL;
|
||||
@@ -411,25 +501,27 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 Pow2Mod(ulong exponent, GpuUInt128 modulus)
|
||||
public static GpuUInt128 Pow2Mod(ulong exponent, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.IsZero || modulus == One)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
// This should never happen in production code.
|
||||
// if (modulus.IsZero || modulus == One)
|
||||
// {
|
||||
// return new();
|
||||
// }
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return new GpuUInt128(1UL);
|
||||
}
|
||||
// This should never happen in production code.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return new GpuUInt128(1UL);
|
||||
// }
|
||||
|
||||
Span<GpuUInt128> oddPowers = stackalloc GpuUInt128[Pow2WindowOddPowerCount];
|
||||
GpuUInt128 power = new GpuUInt128(1UL);
|
||||
GpuUInt128[] oddPowers = new GpuUInt128[PerfectNumberConstants.MaxOddPowersCount];
|
||||
GpuUInt128 power = new(1UL);
|
||||
int oddIndex = 0;
|
||||
|
||||
for (int bit = 1; bit <= Pow2WindowMaxExponent; bit++)
|
||||
{
|
||||
power += power;
|
||||
power.Add(power);
|
||||
if (power.CompareTo(modulus) >= 0)
|
||||
{
|
||||
power.Sub(modulus);
|
||||
@@ -437,12 +529,13 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
|
||||
if ((bit & 1) != 0)
|
||||
{
|
||||
oddPowers[oddIndex++] = power;
|
||||
oddPowers[oddIndex] = power;
|
||||
oddIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
GpuUInt128 result = new GpuUInt128(1UL);
|
||||
int bitLength = BitOperations.Log2(exponent) + 1;
|
||||
GpuUInt128 result = GpuUInt128.One;
|
||||
int bitLength = GetScalarBitLength(exponent);
|
||||
int index = bitLength - 1;
|
||||
|
||||
while (index >= 0)
|
||||
@@ -483,6 +576,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
|
||||
return result;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int TrailingZeroCount(in ReadOnlyGpuUInt128 value)
|
||||
{
|
||||
if (value.Low != 0UL)
|
||||
{
|
||||
return BitOperations.TrailingZeroCount(value.Low);
|
||||
}
|
||||
|
||||
return 64 + BitOperations.TrailingZeroCount(value.High);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int TrailingZeroCount(GpuUInt128 value)
|
||||
{
|
||||
@@ -539,21 +643,66 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||
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);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
||||
{
|
||||
if (CompareTo(value) >= 0)
|
||||
{
|
||||
Sub(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(modulus);
|
||||
Sub(value);
|
||||
}
|
||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(ulong value, GpuUInt128 modulus)
|
||||
public void SubMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SubModInternal(ulong valueHigh, ulong valueLow, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (CompareToRaw(valueHigh, valueLow) >= 0)
|
||||
{
|
||||
SubInternal(valueHigh, valueLow);
|
||||
return;
|
||||
}
|
||||
|
||||
Add(in modulus);
|
||||
SubInternal(valueHigh, valueLow);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int CompareToRaw(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
if (High < otherHigh)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (High > otherHigh)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Low < otherLow)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Low > otherLow)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(ulong value, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
ulong low = Low;
|
||||
ulong high = High;
|
||||
@@ -568,10 +717,21 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
Low = low - value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Mul(in ReadOnlyGpuUInt128 other)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
||||
Multiply(readOnlyThis, other, out var high, out var low);
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Mul(GpuUInt128 other)
|
||||
{
|
||||
Multiply(this, other, out var high, out var low);
|
||||
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
||||
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
||||
Multiply(readOnlyThis, readOnlyOther, out var high, out var low);
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
@@ -585,14 +745,15 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 DivideExact(GpuUInt128 dividend, GpuUInt128 divisor)
|
||||
public static GpuUInt128 DivideExact(in ReadOnlyGpuUInt128 dividend, in ReadOnlyGpuUInt128 divisor)
|
||||
{
|
||||
int divisorBits = divisor.GetBitLength();
|
||||
int shift = dividend.GetBitLength() - divisorBits;
|
||||
|
||||
GpuUInt128 quotient = Zero;
|
||||
GpuUInt128 remainder = dividend;
|
||||
GpuUInt128 shiftedDivisor = divisor << shift;
|
||||
GpuUInt128 shiftedDivisor = divisor;
|
||||
shiftedDivisor.ShiftLeft(shift);
|
||||
GpuUInt128 quotientBit = One << shift;
|
||||
|
||||
while (true)
|
||||
@@ -622,7 +783,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Xor(GpuUInt128 other)
|
||||
public void Xor(in ReadOnlyGpuUInt128 other)
|
||||
{
|
||||
High ^= other.High;
|
||||
Low ^= other.Low;
|
||||
@@ -682,7 +843,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
High = high >> shift;
|
||||
}
|
||||
|
||||
public void MulMod(GpuUInt128 other, GpuUInt128 modulus)
|
||||
public void MulMod(in ReadOnlyGpuUInt128 other, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -713,7 +874,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
/// method is intended for validation only and should not be used inside
|
||||
/// GPU kernels.
|
||||
/// </summary>
|
||||
internal void MulModBigInteger(GpuUInt128 other, GpuUInt128 modulus)
|
||||
internal void MulModBigInteger(in ReadOnlyGpuUInt128 other, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
var left = (BigInteger)(UInt128)this;
|
||||
var right = (BigInteger)(UInt128)other;
|
||||
@@ -728,7 +889,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
/// repeated subtractions and becomes extremely slow for large remainders.
|
||||
/// Kept for future optimization work.
|
||||
/// </summary>
|
||||
internal void MulModByLimb(GpuUInt128 other, GpuUInt128 modulus)
|
||||
internal void MulModByLimb(in ReadOnlyGpuUInt128 other, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
// TODO: Relocate this limb-based reducer to the benchmark project once the production
|
||||
// pipeline switches to the faster allocating legacy path demonstrated in the benchmarks.
|
||||
@@ -757,7 +918,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void MulMod(ulong value, GpuUInt128 modulus)
|
||||
public void MulMod(ulong value, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -771,7 +932,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ulong MulMod(GpuUInt128 other, ulong modulus)
|
||||
public readonly ulong MulMod(in ReadOnlyGpuUInt128 other, ulong modulus)
|
||||
{
|
||||
ulong modulusLocal = modulus;
|
||||
ulong a = Low % modulusLocal;
|
||||
@@ -855,7 +1016,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ReduceProductBitwise(ulong p3, ulong p2, ulong p1, ulong p0, GpuUInt128 modulus)
|
||||
private void ReduceProductBitwise(ulong p3, ulong p2, ulong p1, ulong p0, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
GpuUInt128 remainder = new();
|
||||
int bit;
|
||||
@@ -945,7 +1106,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ulong MulModMontgomery64(GpuUInt128 other, ulong modulus, ulong nPrime, ulong r2)
|
||||
public readonly ulong MulModMontgomery64(in ReadOnlyGpuUInt128 other, ulong modulus, ulong nPrime, ulong r2)
|
||||
{
|
||||
// TODO: Retire this struct-based Montgomery path from production after adopting the extension
|
||||
// helper, which benchmarks 6-7× faster across dense and near-modulus operands.
|
||||
@@ -984,7 +1145,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SquareMod(GpuUInt128 modulus)
|
||||
public void SquareMod(in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -1043,7 +1204,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
Low = remainder.Low;
|
||||
}
|
||||
|
||||
public void ModPow(GpuUInt128 exponent, GpuUInt128 modulus)
|
||||
public void ModPow(GpuUInt128 exponent, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
// TODO: Swap this copy-heavy path for the pooled base/exponent ladder used in
|
||||
// GpuUInt128MulModBenchmarks so the GPU shim keeps reusing buffers instead of
|
||||
@@ -1069,7 +1230,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
Low = result.Low;
|
||||
}
|
||||
|
||||
public void ModPow(ulong exponent, GpuUInt128 modulus)
|
||||
public void ModPow(ulong exponent, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
// TODO: Share the pooled ladder state from GpuUInt128MulModBenchmarks here as well so
|
||||
// Lucas–Lehmer batches avoid constructing throwaway temporaries on every exponent.
|
||||
@@ -1103,7 +1264,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ModInv(GpuUInt128 modulus)
|
||||
public void ModInv(in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -1121,7 +1282,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
((UInt128)this).ToString();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void MultiplyFull(GpuUInt128 left, GpuUInt128 right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
private static void MultiplyFull(in ReadOnlyGpuUInt128 left, in ReadOnlyGpuUInt128 right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
// TODO: Reuse variables to reduce register pressure following the fused-limb layout
|
||||
// from GpuUInt128MulModByLimbBenchmarks so the multiply helper matches the fastest
|
||||
@@ -1167,7 +1328,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void MultiplyFull(GpuUInt128 left, ulong right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
private static void MultiplyFull(in ReadOnlyGpuUInt128 left, ulong right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
var (highLow, lowLow) = Mul64(left.Low, right);
|
||||
var (highHigh, lowHigh) = Mul64(left.High, right);
|
||||
@@ -1184,7 +1345,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Multiply(GpuUInt128 left, GpuUInt128 right, out ulong high, out ulong low)
|
||||
private static void Multiply(in ReadOnlyGpuUInt128 left, in ReadOnlyGpuUInt128 right, out ulong high, out ulong low)
|
||||
{
|
||||
// TODO: Reuse variables to reduce register pressure.
|
||||
var (h0, l0) = Mul64(left.Low, right.Low);
|
||||
@@ -1219,7 +1380,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void SquareFull(GpuUInt128 value, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
private static void SquareFull(in ReadOnlyGpuUInt128 value, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
// TODO: Reuse variables to reduce register pressure.
|
||||
// Compute (H*2^64 + L)^2 = H^2*2^128 + 2*H*L*2^64 + L^2
|
||||
@@ -1457,14 +1618,14 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if the TODO below is still relevant.
|
||||
// TODO: Check if the TODO below is still relevant.
|
||||
|
||||
// TODO(MOD-OPT): Montgomery/Barrett integration plan
|
||||
// - Introduce caches of modulus-dependent constants:
|
||||
// * Montgomery: n' (-(n^{-1}) mod 2^64 or 2^128), R2 = (R^2 mod n)
|
||||
// * Barrett: mu = floor(2^k / n) for k ∈ {128, 192, 256}
|
||||
// - Add fast-path for 64-bit NTT primes (modulus.High == 0UL) using pure 64-bit ops.
|
||||
// - Expose helpers to retrieve/calc constants once per modulus and reuse in kernels.
|
||||
// - Wire these into MulMod and SquareMod hot paths under feature toggles.
|
||||
// - Ensure ILGPU compatibility (no BigInteger, no % inside kernels).
|
||||
// TODO(MOD-OPT): Montgomery/Barrett integration plan
|
||||
// - Introduce caches of modulus-dependent constants:
|
||||
// * Montgomery: n' (-(n^{-1}) mod 2^64 or 2^128), R2 = (R^2 mod n)
|
||||
// * Barrett: mu = floor(2^k / n) for k ∈ {128, 192, 256}
|
||||
// - Add fast-path for 64-bit NTT primes (modulus.High == 0UL) using pure 64-bit ops.
|
||||
// - Expose helpers to retrieve/calc constants once per modulus and reuse in kernels.
|
||||
// - Wire these into MulMod and SquareMod hot paths under feature toggles.
|
||||
// - Ensure ILGPU compatibility (no BigInteger, no % inside kernels).
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Numerics;
|
||||
@@ -13,8 +12,13 @@ using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
/// <summary>
|
||||
/// Scans Mersenne divisors on the GPU for prime exponents p >= 31 using per-divisor Montgomery data.
|
||||
/// </summary>
|
||||
public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
private const int RemainderSlotCount = 4;
|
||||
|
||||
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
||||
private readonly object _sync = new();
|
||||
private ulong _divisorLimit;
|
||||
@@ -24,6 +28,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>> _kernelExponentCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte>> _remainderDeltaKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte>> _remainderScanKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>>> _candidateMaskKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
||||
|
||||
@@ -41,6 +46,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
accelerator,
|
||||
acc => acc.LoadStreamKernel<Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte>(AccumulateRemaindersKernel));
|
||||
|
||||
private Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> GetCandidateMaskKernel(Accelerator accelerator) =>
|
||||
_candidateMaskKernelCache.GetOrAdd(
|
||||
accelerator,
|
||||
acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>>(EvaluateCandidateMaskKernel));
|
||||
|
||||
public int GpuBatchSize
|
||||
{
|
||||
get => _gpuBatchSize;
|
||||
@@ -109,10 +119,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
resources.RemainderBuffer,
|
||||
resources.RemainderDeltaKernel,
|
||||
resources.RemainderScanKernel,
|
||||
resources.CandidateMaskKernel,
|
||||
resources.Divisors,
|
||||
resources.Exponents,
|
||||
resources.Hits,
|
||||
resources.DivisorData,
|
||||
resources.CycleCandidates,
|
||||
resources.CycleLengths,
|
||||
resources.CycleCapacity,
|
||||
out lastProcessed,
|
||||
out coveredRange,
|
||||
out processedCount
|
||||
@@ -172,10 +186,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
MemoryBuffer1D<byte, Stride1D.Dense> remainderBuffer,
|
||||
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> remainderDeltaKernel,
|
||||
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> remainderScanKernel,
|
||||
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> candidateMaskKernel,
|
||||
in ulong[] divisors,
|
||||
ulong[] exponents,
|
||||
byte[] hits,
|
||||
MontgomeryDivisorData[] divisorData,
|
||||
ulong[] cycleCandidates,
|
||||
ulong[] cycleLengths,
|
||||
int cycleCapacity,
|
||||
out ulong lastProcessed,
|
||||
out bool coveredRange,
|
||||
out ulong processedCount)
|
||||
@@ -194,27 +212,22 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
||||
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
||||
|
||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||
int cycleBatchCapacity = Math.Min(batchCapacity, cycleCache.PreferredBatchSize);
|
||||
int maxKernelGroupSize = (int)accelerator.MaxNumThreadsPerGroup;
|
||||
if (cycleBatchCapacity > maxKernelGroupSize)
|
||||
cycleCapacity = Math.Max(1, cycleCapacity);
|
||||
|
||||
int chunkCapacity = Math.Min(batchCapacity, cycleCapacity);
|
||||
int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup;
|
||||
if (maxThreadsPerGroup > 0)
|
||||
{
|
||||
cycleBatchCapacity = maxKernelGroupSize;
|
||||
chunkCapacity = Math.Min(chunkCapacity, maxThreadsPerGroup);
|
||||
}
|
||||
|
||||
if (cycleBatchCapacity <= 0)
|
||||
if (chunkCapacity <= 0)
|
||||
{
|
||||
cycleBatchCapacity = batchCapacity;
|
||||
chunkCapacity = cycleCapacity;
|
||||
}
|
||||
|
||||
ulong[] candidateDivisors = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||
ulong[] filteredCycles = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||
byte[] remainder10Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||
byte[] remainder8Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||
byte[] remainder5Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||
byte[] remainder3Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
||||
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
||||
|
||||
UInt128 twoP128 = (UInt128)prime << 1;
|
||||
if (twoP128 == UInt128.Zero)
|
||||
@@ -252,12 +265,20 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
byte remainder5 = (byte)((ulong)(currentDivisor128 % 5UL));
|
||||
byte remainder3 = (byte)((ulong)(currentDivisor128 % 3UL));
|
||||
bool lastIsSeven = (prime & 3UL) == 3UL;
|
||||
byte lastIsSevenFlag = lastIsSeven ? (byte)1 : (byte)0;
|
||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||
ArrayView1D<byte, Stride1D.Dense> remainderBaseView = remainderBuffer.View;
|
||||
int remainderStride = (int)divisorDeltaBuffer.Length;
|
||||
if (remainderStride <= 0)
|
||||
{
|
||||
remainderStride = chunkCapacity;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (currentK <= maxK)
|
||||
{
|
||||
int chunkCount = Math.Min(cycleBatchCapacity, batchCapacity);
|
||||
int chunkCount = Math.Min(chunkCapacity, batchCapacity);
|
||||
ulong remainingK = maxK - currentK + 1UL;
|
||||
if ((ulong)chunkCount > remainingK)
|
||||
{
|
||||
@@ -269,12 +290,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
break;
|
||||
}
|
||||
|
||||
Span<ulong> candidateSpan = candidateDivisors.AsSpan(0, chunkCount);
|
||||
Span<ulong> candidateSpan = cycleCandidates.AsSpan(0, chunkCount);
|
||||
Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount);
|
||||
Span<byte> rem10Span = remainder10Buffer.AsSpan(0, chunkCount);
|
||||
Span<byte> rem8Span = remainder8Buffer.AsSpan(0, chunkCount);
|
||||
Span<byte> rem5Span = remainder5Buffer.AsSpan(0, chunkCount);
|
||||
Span<byte> rem3Span = remainder3Buffer.AsSpan(0, chunkCount);
|
||||
// Reuse the hits array as temporary storage for the candidate mask before it stores final GPU hit flags.
|
||||
Span<byte> maskSpan = hits.AsSpan(0, chunkCount);
|
||||
|
||||
UInt128 localDivisor = currentDivisor128;
|
||||
|
||||
@@ -299,49 +318,37 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
ArrayView1D<ulong, Stride1D.Dense> gapView = divisorDeltaBuffer.View.SubView(0, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> deltaView = remainderDeltaBuffer.View.SubView(0, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainderView = remainderBuffer.View.SubView(0, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder10View = remainderBaseView.SubView(0, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder8View = remainderBaseView.SubView(remainderStride, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder5View = remainderBaseView.SubView(remainderStride * 2, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder3View = remainderBaseView.SubView(remainderStride * 3, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> maskView = hitsBuffer.View.SubView(0, chunkCount);
|
||||
|
||||
gapView.CopyFromCPU(ref MemoryMarshal.GetReference(gapSpan), chunkCount);
|
||||
|
||||
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder10, 10, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem10Span);
|
||||
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder8, 8, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem8Span);
|
||||
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder5, 5, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem5Span);
|
||||
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder3, 3, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem3Span);
|
||||
ComputeRemaindersOnGpu(chunkCount, remainder10, 10, gapView, deltaView, remainder10View, remainderDeltaKernel, remainderScanKernel);
|
||||
ComputeRemaindersOnGpu(chunkCount, remainder8, 8, gapView, deltaView, remainder8View, remainderDeltaKernel, remainderScanKernel);
|
||||
ComputeRemaindersOnGpu(chunkCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel);
|
||||
ComputeRemaindersOnGpu(chunkCount, remainder3, 3, gapView, deltaView, remainder3View, remainderDeltaKernel, remainderScanKernel);
|
||||
|
||||
remainder10 = AddMod(rem10Span[chunkCount - 1], step10, 10);
|
||||
remainder8 = AddMod(rem8Span[chunkCount - 1], step8, 8);
|
||||
remainder5 = AddMod(rem5Span[chunkCount - 1], step5, 5);
|
||||
remainder3 = AddMod(rem3Span[chunkCount - 1], step3, 3);
|
||||
BuildCandidateMaskOnGpu(chunkCount, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView, maskSpan, candidateMaskKernel);
|
||||
|
||||
byte lastRemainder10 = FetchLastRemainder(remainder10View, chunkCount);
|
||||
byte lastRemainder8 = FetchLastRemainder(remainder8View, chunkCount);
|
||||
byte lastRemainder5 = FetchLastRemainder(remainder5View, chunkCount);
|
||||
byte lastRemainder3 = FetchLastRemainder(remainder3View, chunkCount);
|
||||
|
||||
remainder10 = AddMod(lastRemainder10, step10, 10);
|
||||
remainder8 = AddMod(lastRemainder8, step8, 8);
|
||||
remainder5 = AddMod(lastRemainder5, step5, 5);
|
||||
remainder3 = AddMod(lastRemainder3, step3, 3);
|
||||
|
||||
int filteredCount = 0;
|
||||
Span<ulong> filteredDivisorsSpan = filteredDivisors.AsSpan(0, chunkCount);
|
||||
Span<ulong> filteredCyclesSpan = filteredCycles.AsSpan(0, chunkCount);
|
||||
|
||||
for (int i = 0; i < chunkCount; i++)
|
||||
{
|
||||
byte r10 = rem10Span[i];
|
||||
if (lastIsSeven)
|
||||
{
|
||||
if (r10 != 3 && r10 != 7 && r10 != 9)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r10 != 1 && r10 != 3 && r10 != 7 && r10 != 9)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
byte r8 = rem8Span[i];
|
||||
if (r8 != 1 && r8 != 7)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rem3Span[i] == 0 || rem5Span[i] == 0)
|
||||
if (maskSpan[i] == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -357,70 +364,47 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
continue;
|
||||
}
|
||||
|
||||
cycleCache.GetCycleLengths(filteredDivisorsSpan[..filteredCount], filteredCyclesSpan[..filteredCount]);
|
||||
Span<ulong> cycleSpan = cycleLengths.AsSpan(0, filteredCount);
|
||||
cycleCache.GetCycleLengths(filteredDivisorsSpan[..filteredCount], cycleSpan);
|
||||
|
||||
int batchSize = 0;
|
||||
int admissibleCount = 0;
|
||||
for (int i = 0; i < filteredCount; i++)
|
||||
{
|
||||
ulong divisorValue = filteredDivisorsSpan[i];
|
||||
ulong cycleLength = filteredCyclesSpan[i];
|
||||
|
||||
bool includeDivisor = true;
|
||||
ulong exponent = prime;
|
||||
|
||||
if (cycleLength > 0UL)
|
||||
if (cycleSpan[i] == prime)
|
||||
{
|
||||
ulong remainder = prime;
|
||||
if (remainder >= cycleLength)
|
||||
{
|
||||
remainder %= cycleLength;
|
||||
}
|
||||
|
||||
if (remainder != 0UL)
|
||||
{
|
||||
includeDivisor = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
exponent = 0UL;
|
||||
}
|
||||
filteredDivisorsSpan[admissibleCount++] = filteredDivisorsSpan[i];
|
||||
}
|
||||
|
||||
if (!includeDivisor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
divisors[batchSize] = divisorValue;
|
||||
exponents[batchSize] = exponent;
|
||||
batchSize++;
|
||||
}
|
||||
|
||||
if (batchSize == 0)
|
||||
if (admissibleCount == 0)
|
||||
{
|
||||
processedAll = currentK > maxK;
|
||||
continue;
|
||||
}
|
||||
|
||||
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
||||
divisorSpan = divisors.AsSpan(0, batchSize);
|
||||
exponentSpan = exponents.AsSpan(0, batchSize);
|
||||
hitsSpan = hits.AsSpan(0, batchSize);
|
||||
divisorView = divisorsBuffer.View.SubView(0, batchSize);
|
||||
exponentView = exponentBuffer.View.SubView(0, batchSize);
|
||||
hitsView = hitsBuffer.View.SubView(0, batchSize);
|
||||
divisorDataSpan = divisorData.AsSpan(0, admissibleCount);
|
||||
divisorSpan = divisors.AsSpan(0, admissibleCount);
|
||||
exponentSpan = exponents.AsSpan(0, admissibleCount);
|
||||
// Reassign the hits span to store Montgomery kernel results after the mask data has been consumed.
|
||||
hitsSpan = hits.AsSpan(0, admissibleCount);
|
||||
divisorView = divisorsBuffer.View.SubView(0, admissibleCount);
|
||||
exponentView = exponentBuffer.View.SubView(0, admissibleCount);
|
||||
hitsView = hitsBuffer.View.SubView(0, admissibleCount);
|
||||
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
for (int i = 0; i < admissibleCount; i++)
|
||||
{
|
||||
divisorDataSpan[i] = MontgomeryDivisorDataCache.Get(divisorSpan[i]);
|
||||
ulong divisorValue = filteredDivisorsSpan[i];
|
||||
divisorSpan[i] = divisorValue;
|
||||
exponentSpan[i] = prime;
|
||||
divisorDataSpan[i] = MontgomeryDivisorData.FromModulus(divisorValue);
|
||||
}
|
||||
|
||||
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), batchSize);
|
||||
kernel(batchSize, divisorView, exponentView, hitsView);
|
||||
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize);
|
||||
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount);
|
||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), admissibleCount);
|
||||
kernel(admissibleCount, divisorView, exponentView, hitsView);
|
||||
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), admissibleCount);
|
||||
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
for (int i = 0; i < admissibleCount; i++)
|
||||
{
|
||||
if (hitsSpan[i] != 0)
|
||||
{
|
||||
@@ -432,7 +416,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
if (!composite)
|
||||
{
|
||||
lastProcessed = divisorSpan[batchSize - 1];
|
||||
lastProcessed = divisorSpan[admissibleCount - 1];
|
||||
}
|
||||
|
||||
if (composite)
|
||||
@@ -445,14 +429,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(candidateDivisors, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(filteredDivisors, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(filteredCycles, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(divisorGaps, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(remainder10Buffer, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(remainder8Buffer, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(remainder5Buffer, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(remainder3Buffer, clearArray: false);
|
||||
}
|
||||
|
||||
coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128);
|
||||
@@ -460,7 +438,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeRemaindersOnGpu(
|
||||
Accelerator accelerator,
|
||||
int count,
|
||||
byte baseRemainder,
|
||||
byte modulus,
|
||||
@@ -468,8 +445,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
ArrayView1D<byte, Stride1D.Dense> deltaView,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainderView,
|
||||
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel,
|
||||
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel,
|
||||
Span<byte> destination)
|
||||
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
@@ -483,11 +459,42 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
KernelConfig kernelConfig = new KernelConfig(gridDim, groupDim, sharedMemoryConfig);
|
||||
|
||||
scanKernel(kernelConfig, new Index1D(count), deltaView, remainderView, baseRemainder, modulus);
|
||||
accelerator.Synchronize();
|
||||
remainderView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void BuildCandidateMaskOnGpu(
|
||||
int count,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder10View,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder8View,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder5View,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder3View,
|
||||
byte lastIsSevenFlag,
|
||||
ArrayView1D<byte, Stride1D.Dense> maskView,
|
||||
Span<byte> destination,
|
||||
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> maskKernel)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
destination.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView);
|
||||
maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
||||
}
|
||||
|
||||
private static byte FetchLastRemainder(ArrayView1D<byte, Stride1D.Dense> remainderView, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte value = 0;
|
||||
remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static byte AddMod(byte value, byte delta, byte modulus)
|
||||
{
|
||||
int result = value + delta;
|
||||
@@ -544,7 +551,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
}
|
||||
|
||||
ulong exponent = exponents[index];
|
||||
hits[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
||||
hits[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
@@ -556,8 +563,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentsBuffer;
|
||||
private MemoryBuffer1D<ulong, Stride1D.Dense> _resultsBuffer;
|
||||
private ulong[] _hostBuffer;
|
||||
private int[]? _positionBuffer;
|
||||
private Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? _factorCache;
|
||||
private int _capacity;
|
||||
private bool _disposed;
|
||||
|
||||
@@ -571,169 +576,54 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
_exponentsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||
_resultsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||
_hostBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||
_positionBuffer = null;
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
_disposed = false;
|
||||
_factorCache?.Clear();
|
||||
}
|
||||
|
||||
public void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(DivisorScanSession));
|
||||
}
|
||||
|
||||
int primesLength = primes.Length;
|
||||
if (primesLength == 0)
|
||||
int length = primes.Length;
|
||||
if (length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
var exponentStepper = new ExponentRemainderStepper(divisorData);
|
||||
if (!exponentStepper.IsValidModulus)
|
||||
if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL)
|
||||
{
|
||||
hits.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> cache = _factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(16);
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(divisor, primes[0], cache, out divisorCycle) || divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
hits.Clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cycleStepper = new CycleRemainderStepper(divisorCycle);
|
||||
ulong initialCycleRemainder = cycleStepper.Initialize(primes[0]);
|
||||
ProcessWithCycle(ref exponentStepper, ref cycleStepper, initialCycleRemainder, divisorCycle, divisorData, primes, hits);
|
||||
}
|
||||
|
||||
|
||||
private void ProcessWithCycle(
|
||||
ref ExponentRemainderStepper exponentStepper,
|
||||
ref CycleRemainderStepper cycleStepper,
|
||||
ulong initialCycleRemainder,
|
||||
ulong divisorCycle,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
ReadOnlySpan<ulong> primes,
|
||||
Span<byte> hits)
|
||||
{
|
||||
Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> kernel = _kernel;
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentsView = _exponentsBuffer.View;
|
||||
ArrayView1D<ulong, Stride1D.Dense> resultsView = _resultsBuffer.View;
|
||||
int capacity = _capacity;
|
||||
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, capacity);
|
||||
int[]? positionBuffer = _positionBuffer;
|
||||
if (positionBuffer is null)
|
||||
{
|
||||
// Session construction already enforces the batch capacity, so we only
|
||||
// need to rent the position buffer on the first cycle-enabled invocation.
|
||||
positionBuffer = ArrayPool<int>.Shared.Rent(capacity);
|
||||
_positionBuffer = positionBuffer;
|
||||
}
|
||||
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, _capacity);
|
||||
|
||||
Span<int> positionSpan = positionBuffer.AsSpan(0, capacity);
|
||||
|
||||
int gpuBatchSize = _owner._gpuBatchSize;
|
||||
int primesLength = primes.Length;
|
||||
int offset = 0;
|
||||
bool cycleRemainderPending = true;
|
||||
|
||||
while (offset < primesLength)
|
||||
while (offset < length)
|
||||
{
|
||||
int batchSize = Math.Min(gpuBatchSize, primesLength - offset);
|
||||
ReadOnlySpan<ulong> primesSlice = primes.Slice(offset, batchSize);
|
||||
Span<byte> hitsSlice = hits.Slice(offset, batchSize);
|
||||
int batchSize = Math.Min(_capacity, length - offset);
|
||||
ReadOnlySpan<ulong> batch = primes.Slice(offset, batchSize);
|
||||
batch.CopyTo(hostSpan);
|
||||
|
||||
int computeCount = 0;
|
||||
bool hasState = exponentStepper.HasState;
|
||||
ulong previousExponent = exponentStepper.PreviousExponent;
|
||||
bool useInitialRemainder = cycleRemainderPending;
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentSlice = exponentsView.SubView(0, batchSize);
|
||||
exponentSlice.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), batchSize);
|
||||
|
||||
ArrayView1D<ulong, Stride1D.Dense> resultSlice = resultsView.SubView(0, batchSize);
|
||||
_kernel(batchSize, divisorData, exponentSlice, resultSlice);
|
||||
resultSlice.CopyToCPU(ref MemoryMarshal.GetReference(hostSpan), batchSize);
|
||||
|
||||
Span<byte> hitSlice = hits.Slice(offset, batchSize);
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
{
|
||||
hitsSlice[i] = 0;
|
||||
ulong primeValue = primesSlice[i];
|
||||
|
||||
ulong remainder = useInitialRemainder
|
||||
? initialCycleRemainder
|
||||
: cycleStepper.ComputeNext(primeValue);
|
||||
useInitialRemainder = false;
|
||||
|
||||
if (remainder != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong workValue;
|
||||
if (!hasState)
|
||||
{
|
||||
workValue = primeValue;
|
||||
hasState = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
workValue = primeValue - previousExponent;
|
||||
}
|
||||
|
||||
previousExponent = primeValue;
|
||||
|
||||
if (workValue >= divisorCycle)
|
||||
{
|
||||
// TODO: Replace this `%` with the divisor-cycle remainder helper so we reuse cached
|
||||
// modulo results instead of recomputing them in the GPU staging loop.
|
||||
workValue %= divisorCycle;
|
||||
}
|
||||
|
||||
hostSpan[computeCount] = workValue;
|
||||
positionSpan[computeCount] = i;
|
||||
computeCount++;
|
||||
}
|
||||
|
||||
cycleRemainderPending = useInitialRemainder;
|
||||
|
||||
if (computeCount > 0)
|
||||
{
|
||||
Span<ulong> exponentSlice = hostSpan[..computeCount];
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentsView.SubView(0, computeCount);
|
||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
||||
|
||||
ArrayView1D<ulong, Stride1D.Dense> resultView = resultsView.SubView(0, computeCount);
|
||||
kernel(computeCount, divisorData, exponentView, resultView);
|
||||
resultView.CopyToCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
||||
|
||||
bool requiresInitialization = !exponentStepper.HasState;
|
||||
for (int i = 0; i < computeCount; i++)
|
||||
{
|
||||
int position = positionSpan[i];
|
||||
ulong primeValue = primesSlice[position];
|
||||
ulong montgomeryResult = exponentSlice[i];
|
||||
bool isUnity;
|
||||
|
||||
if (requiresInitialization)
|
||||
{
|
||||
exponentStepper.TryInitializeFromMontgomeryResult(primeValue, montgomeryResult, out isUnity);
|
||||
requiresInitialization = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
exponentStepper.TryAdvanceWithMontgomeryDelta(primeValue, montgomeryResult, out isUnity);
|
||||
}
|
||||
|
||||
hitsSlice[position] = isUnity ? (byte)1 : (byte)0;
|
||||
}
|
||||
hitSlice[i] = hostSpan[i] == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
offset += batchSize;
|
||||
@@ -748,11 +638,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_factorCache?.Clear();
|
||||
_owner._sessionPool.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ComputeRemainderDeltasKernel(Index1D index, ArrayView<ulong> gaps, ArrayView<byte> deltas, byte modulus)
|
||||
{
|
||||
deltas[index] = (byte)(gaps[index] % modulus);
|
||||
@@ -822,6 +710,55 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
remainders[globalIndex] = (byte)remainder;
|
||||
}
|
||||
|
||||
private static void EvaluateCandidateMaskKernel(
|
||||
Index1D index,
|
||||
ArrayView<byte> remainder10,
|
||||
ArrayView<byte> remainder8,
|
||||
ArrayView<byte> remainder5,
|
||||
ArrayView<byte> remainder3,
|
||||
byte lastIsSevenFlag,
|
||||
ArrayView<byte> mask)
|
||||
{
|
||||
int globalIndex = index;
|
||||
int length = (int)mask.Length;
|
||||
if (globalIndex >= length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte value10 = remainder10[globalIndex];
|
||||
bool accept10;
|
||||
if (lastIsSevenFlag != 0)
|
||||
{
|
||||
accept10 = value10 == 3 || value10 == 7 || value10 == 9;
|
||||
}
|
||||
else
|
||||
{
|
||||
accept10 = value10 == 1 || value10 == 3 || value10 == 7 || value10 == 9;
|
||||
}
|
||||
|
||||
if (!accept10)
|
||||
{
|
||||
mask[globalIndex] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
byte value8 = remainder8[globalIndex];
|
||||
if (value8 != 1 && value8 != 7)
|
||||
{
|
||||
mask[globalIndex] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (remainder3[globalIndex] == 0 || remainder5[globalIndex] == 0)
|
||||
{
|
||||
mask[globalIndex] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
mask[globalIndex] = 1;
|
||||
}
|
||||
|
||||
private static void ComputeMontgomeryExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
@@ -833,7 +770,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
ulong exponent = exponents[index];
|
||||
|
||||
results[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: true);
|
||||
results[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: true);
|
||||
}
|
||||
|
||||
public ulong DivisorLimit
|
||||
@@ -869,19 +806,31 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
{
|
||||
internal BatchResources(MersenneNumberDivisorByDivisorGpuTester owner, Accelerator accelerator, int capacity)
|
||||
{
|
||||
DivisorsBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(capacity);
|
||||
ExponentBuffer = accelerator.Allocate1D<ulong>(capacity);
|
||||
HitsBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||
DivisorDeltaBuffer = accelerator.Allocate1D<ulong>(capacity);
|
||||
RemainderDeltaBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||
RemainderBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||
Divisors = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||
Exponents = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||
Hits = ArrayPool<byte>.Shared.Rent(capacity);
|
||||
DivisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(capacity);
|
||||
int cycleCapacity = DivisorCycleCache.Shared.PreferredBatchSize;
|
||||
if (cycleCapacity <= 0)
|
||||
{
|
||||
cycleCapacity = 1;
|
||||
}
|
||||
|
||||
int actualCapacity = Math.Max(capacity, cycleCapacity);
|
||||
|
||||
DivisorsBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(actualCapacity);
|
||||
ExponentBuffer = accelerator.Allocate1D<ulong>(actualCapacity);
|
||||
HitsBuffer = accelerator.Allocate1D<byte>(actualCapacity);
|
||||
DivisorDeltaBuffer = accelerator.Allocate1D<ulong>(actualCapacity);
|
||||
RemainderDeltaBuffer = accelerator.Allocate1D<byte>(actualCapacity);
|
||||
RemainderBuffer = accelerator.Allocate1D<byte>(actualCapacity * RemainderSlotCount);
|
||||
Divisors = ArrayPool<ulong>.Shared.Rent(actualCapacity);
|
||||
Exponents = ArrayPool<ulong>.Shared.Rent(actualCapacity);
|
||||
Hits = ArrayPool<byte>.Shared.Rent(actualCapacity);
|
||||
DivisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(actualCapacity);
|
||||
CycleCandidates = ArrayPool<ulong>.Shared.Rent(cycleCapacity);
|
||||
CycleLengths = ArrayPool<ulong>.Shared.Rent(cycleCapacity);
|
||||
RemainderDeltaKernel = owner.GetRemainderDeltaKernel(accelerator);
|
||||
RemainderScanKernel = owner.GetRemainderScanKernel(accelerator);
|
||||
Capacity = capacity;
|
||||
CandidateMaskKernel = owner.GetCandidateMaskKernel(accelerator);
|
||||
Capacity = actualCapacity;
|
||||
CycleCapacity = cycleCapacity;
|
||||
}
|
||||
|
||||
internal readonly MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> DivisorsBuffer;
|
||||
@@ -904,12 +853,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
internal readonly MontgomeryDivisorData[] DivisorData;
|
||||
|
||||
internal readonly ulong[] CycleCandidates;
|
||||
|
||||
internal readonly ulong[] CycleLengths;
|
||||
|
||||
internal readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> RemainderDeltaKernel;
|
||||
|
||||
internal readonly Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> RemainderScanKernel;
|
||||
internal readonly Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> CandidateMaskKernel;
|
||||
|
||||
internal readonly int Capacity;
|
||||
|
||||
internal readonly int CycleCapacity;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DivisorsBuffer.Dispose();
|
||||
@@ -922,6 +878,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
ArrayPool<ulong>.Shared.Return(Exponents, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(Hits, clearArray: false);
|
||||
ArrayPool<MontgomeryDivisorData>.Shared.Return(DivisorData, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(CycleCandidates, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(CycleLengths, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,16 @@ using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
/// <summary>
|
||||
/// Provides GPU-accelerated divisibility checks for Mersenne numbers with prime exponents p >= 31.
|
||||
/// </summary>
|
||||
public sealed class MersenneNumberDivisorGpuTester
|
||||
{
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, GpuUInt128, ArrayView<byte>>> _kernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>> _kernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, MemoryBuffer1D<byte, Stride1D.Dense>> _resultBuffers = new();
|
||||
|
||||
private Action<Index1D, ulong, GpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
|
||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<byte>>(Kernel));
|
||||
private Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
|
||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(Kernel));
|
||||
|
||||
public static void BuildDivisorCandidates()
|
||||
{
|
||||
@@ -33,106 +36,102 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
_divisorCandidates = count == 0 ? [] : list[..count];
|
||||
}
|
||||
|
||||
public bool IsDivisible(ulong exponent, UInt128 divisor)
|
||||
{
|
||||
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernel = GetKernel(accelerator);
|
||||
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
|
||||
resultBuffer.MemSetToZero();
|
||||
kernel(1, exponent, (GpuUInt128)divisor, resultBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
Span<byte> result = stackalloc byte[1];
|
||||
resultBuffer.View.CopyToCPU(ref result[0], 1);
|
||||
bool divisible = result[0] != 0;
|
||||
result[0] = 0;
|
||||
resultBuffer.View.CopyFromCPU(ref result[0], 1);
|
||||
accelerator.Synchronize();
|
||||
gpu.Dispose();
|
||||
return divisible;
|
||||
}
|
||||
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
|
||||
{
|
||||
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernel = GetKernel(accelerator);
|
||||
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
|
||||
resultBuffer.MemSetToZero();
|
||||
kernel(1, exponent, divisor, resultBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
Span<byte> result = stackalloc byte[1];
|
||||
resultBuffer.View.CopyToCPU(ref result[0], 1);
|
||||
bool divisible = result[0] != 0;
|
||||
result[0] = 0;
|
||||
resultBuffer.View.CopyFromCPU(ref result[0], 1);
|
||||
accelerator.Synchronize();
|
||||
gpu.Dispose();
|
||||
return divisible;
|
||||
}
|
||||
|
||||
private static (ulong divisor, uint cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, uint cycle)>();
|
||||
|
||||
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
|
||||
{
|
||||
if (d != UInt128.Zero)
|
||||
{
|
||||
if (IsDivisible(p, d))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyDivisor;
|
||||
|
||||
divisorsExhausted = false;
|
||||
return true;
|
||||
}
|
||||
if (d != UInt128.Zero)
|
||||
{
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(d);
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_divisorCandidates is { Length: > 0 } candidates)
|
||||
divisorsExhausted = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_divisorCandidates is { Length: > 0 } candidates)
|
||||
{
|
||||
int candidateCount = candidates.Length;
|
||||
for (int index = 0; index < candidateCount; index++)
|
||||
{
|
||||
(ulong candidateDivisor, uint cycle) = candidates[index];
|
||||
if (p % cycle != 0UL)
|
||||
{
|
||||
int len = candidates.Length;
|
||||
uint cycle;
|
||||
for (int k = 0; k < len; k++)
|
||||
{
|
||||
(ulong dSmall, cycle) = candidates[k];
|
||||
// TODO: Replace this `%` check with the shared divisor-cycle divisibility helper so GPU scans
|
||||
// reuse the cached cycle remainders instead of recomputing modulo for every candidate.
|
||||
if (p % cycle != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsDivisible(p, dSmall))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(candidateDivisor); // Reusing readOnlyDivisor for candidate divisors.
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UInt128 kMul2;
|
||||
var divisorCycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
|
||||
ulong limit = divisorCyclesSearchLimit;
|
||||
if ((UInt128)limit > maxK2)
|
||||
{
|
||||
limit = (ulong)maxK2;
|
||||
}
|
||||
UInt128 kMul2;
|
||||
var divisorCycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
|
||||
ulong limit = divisorCyclesSearchLimit;
|
||||
if ((UInt128)limit > maxK2)
|
||||
{
|
||||
limit = (ulong)maxK2;
|
||||
}
|
||||
|
||||
ulong k2;
|
||||
for (k2 = 1UL; k2 <= limit; k2++)
|
||||
{
|
||||
kMul2 = (UInt128)k2 << 1;
|
||||
UInt128 candidate = checked(kMul2 * p);
|
||||
d = checked(candidate + UInt128.One);
|
||||
if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (ulong k2 = 1UL; k2 <= limit; k2++)
|
||||
{
|
||||
kMul2 = (UInt128)k2 << 1;
|
||||
UInt128 candidate = checked(kMul2 * p);
|
||||
d = checked(candidate + UInt128.One);
|
||||
if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
|
||||
// TODO: When this lookup falls outside the preloaded snapshot, invoke the single-cycle GPU helper
|
||||
// instead of allowing GetCycle to allocate an entire block; the divisor-cycle benchmarks showed the
|
||||
// one-off computation keeping memory flat while still delivering the optimized stepping data.
|
||||
// TODO: Feed this check through the ProcessEightBitWindows-based residue tracker so we avoid the slow
|
||||
// UInt128 modulo on the CPU fallback highlighted by the Pow2Montgomery benchmarks.
|
||||
if ((UInt128)p % cycle128 != UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
|
||||
if ((UInt128)p % cycle128 != UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsDivisible(p, d))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(d); // Reusing readOnlyDivisor for generated divisors.
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2;
|
||||
return true;
|
||||
}
|
||||
divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Kernel(Index1D _, ulong exponent, GpuUInt128 divisor, ArrayView<byte> result)
|
||||
private static void Kernel(Index1D _, ulong exponent, ReadOnlyGpuUInt128 divisor, ArrayView<byte> result)
|
||||
{
|
||||
if (divisor.High == 0UL && divisor.Low <= 1UL)
|
||||
{
|
||||
@@ -140,16 +139,16 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 baseVal, mod;
|
||||
GpuUInt128 baseVal;
|
||||
int x = divisor.High == 0UL
|
||||
? 63 - XMath.LeadingZeroCount(divisor.Low)
|
||||
: 127 - XMath.LeadingZeroCount(divisor.High);
|
||||
mod = divisor;
|
||||
ReadOnlyGpuUInt128 readOnlyMod = divisor;
|
||||
if (x == 0)
|
||||
{
|
||||
// TODO: Swap Pow2Mod for the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
|
||||
// GpuPow2ModBenchmarks showed the windowed kernel cutting large divisors from ~51 µs to ~21 µs.
|
||||
baseVal = GpuUInt128.Pow2Mod(exponent, mod);
|
||||
baseVal = GpuUInt128.Pow2Mod(exponent, in readOnlyMod);
|
||||
result[0] = baseVal.High == 0UL && baseVal.Low == 1UL ? (byte)1 : (byte)0;
|
||||
return;
|
||||
}
|
||||
@@ -165,12 +164,13 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
}
|
||||
|
||||
GpuUInt128 pow = baseVal;
|
||||
pow.ModPow(exponent / ux, mod);
|
||||
pow.ModPow(exponent / ux, in readOnlyMod);
|
||||
// TODO: Replace this trailing Pow2Mod call with the ProcessEightBitWindows variant once the shared
|
||||
// windowed helper lands so mixed-radix decompositions benefit from the same GPU speedup.
|
||||
var part2 = GpuUInt128.Pow2Mod(exponent % ux, mod);
|
||||
var part2 = GpuUInt128.Pow2Mod(exponent % ux, in readOnlyMod);
|
||||
GpuUInt128 product = pow;
|
||||
product.MulMod(part2, mod);
|
||||
ReadOnlyGpuUInt128 part2ReadOnly = part2.AsReadOnly();
|
||||
product.MulMod(in part2ReadOnly, in readOnlyMod);
|
||||
result[0] = product.High == 0UL && product.Low == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
/// <summary>
|
||||
/// Uses GPU kernels to incrementally scan Mersenne candidates for prime exponents p >= 31.
|
||||
/// </summary>
|
||||
public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool useGpuOrder)
|
||||
{
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
@@ -11,18 +17,23 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
|
||||
public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
|
||||
|
||||
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
||||
var execution = gpuLease.EnterExecutionScope();
|
||||
var accelerator = gpuLease.Accelerator;
|
||||
var stream = gpuLease.Stream;
|
||||
int batchSize = GpuConstants.ScanBatchSize;
|
||||
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
|
||||
UInt128 kStart = 1UL;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
|
||||
byte last = lastIsSeven ? (byte)1 : (byte)0;
|
||||
byte last = lastIsSeven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
|
||||
|
||||
var pow2Kernel = gpuLease.Pow2ModKernel; // TODO: Route this binding to the ProcessEightBitWindows kernel once the scalar helper lands so GPU incremental scans inherit the windowed speedup from GpuPow2ModBenchmarks.
|
||||
var pow2Kernel = gpuLease.Pow2ModKernel;
|
||||
var incKernel = gpuLease.IncrementalKernel;
|
||||
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
|
||||
ulong step10 = (exponent.Mod10() << 1).Mod10();
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong step5 = ((exponent % 5UL) << 1) % 5UL;
|
||||
GpuUInt128 twoPGpu = (GpuUInt128)twoP;
|
||||
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
ResiduePrimeViews primeViews = default;
|
||||
@@ -32,12 +43,13 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
}
|
||||
|
||||
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
|
||||
// Avoid giant stack allocations that can trigger StackOverflow when batchSize is large.
|
||||
// Rent a reusable array from the shared pool instead.
|
||||
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
|
||||
UInt128 remaining;
|
||||
int currentSize;
|
||||
int i;
|
||||
UInt128 q = UInt128.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
while (kStart <= maxK && Volatile.Read(ref isPrime))
|
||||
@@ -45,22 +57,45 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
remaining = maxK - kStart + UInt128.One;
|
||||
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
|
||||
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
|
||||
// Precompute residue automaton bases for this batch
|
||||
UInt128 q0 = twoP * kStart + UInt128.One;
|
||||
// TODO: Swap this multiply-and-add initialization for the divisor-cycle stepping helper so the GPU incremental path reuses cached offsets instead of recomputing twoP multiples every batch.
|
||||
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
|
||||
pow2Kernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul,
|
||||
kernelArgs, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven,
|
||||
primeViews.LastOnePow2, primeViews.LastSevenPow2);
|
||||
pow2Kernel(
|
||||
stream,
|
||||
currentSize,
|
||||
exponent,
|
||||
twoPGpu,
|
||||
(GpuUInt128)kStart,
|
||||
last,
|
||||
divMul,
|
||||
kernelArgs,
|
||||
orderBuffer.View,
|
||||
smallCyclesView,
|
||||
primeViews.LastOne,
|
||||
primeViews.LastSeven,
|
||||
primeViews.LastOnePow2,
|
||||
primeViews.LastSevenPow2);
|
||||
}
|
||||
else
|
||||
{
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
incKernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul,
|
||||
q0m10, q0m8, q0m3, q0m5, orderBuffer.View, smallCyclesView);
|
||||
incKernel(
|
||||
stream,
|
||||
currentSize,
|
||||
exponent,
|
||||
twoPGpu,
|
||||
(GpuUInt128)kStart,
|
||||
last,
|
||||
divMul,
|
||||
q0m10,
|
||||
q0m8,
|
||||
q0m3,
|
||||
q0m5,
|
||||
orderBuffer.View,
|
||||
smallCyclesView);
|
||||
}
|
||||
|
||||
stream.Synchronize();
|
||||
@@ -79,7 +114,6 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
else
|
||||
{
|
||||
q = twoP * kStart + UInt128.One;
|
||||
// TODO: Replace this per-lane multiplication and the subsequent q += twoP stride with the shared divisor-cycle ladder so each iteration advances by cached cycle increments and automatically triggers on-demand GPU computations when the snapshot lacks the requested cycle.
|
||||
for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP)
|
||||
{
|
||||
if (orders[i] != 0UL && q.IsPrimeCandidate())
|
||||
@@ -101,4 +135,3 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,681 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
internal static partial class PrimeOrderGpuHeuristics
|
||||
{
|
||||
internal readonly struct Pow2OddPowerTableGpuUInt128
|
||||
{
|
||||
public Pow2OddPowerTableGpuUInt128(GpuUInt128 baseValue, GpuUInt128 modulus, int oddPowerCount)
|
||||
{
|
||||
Element0 = baseValue;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reusing baseValue to hold base^2 for the shared odd-power ladder that follows.
|
||||
baseValue.MulMod(baseValue, modulus);
|
||||
// TODO: We can calculate baseValue % modulus before loop and use it to increase ladderEntry calculation speed - we'll reuse the base for incremental calculations.
|
||||
GpuUInt128 current = baseValue;
|
||||
|
||||
// We're manually assigning each field to prevent the compiler to initialize each field twice due to auto-initialization. We're using the action to lower the code base size.
|
||||
for (int i = 1; i < oddPowerCount; i++)
|
||||
{
|
||||
current.MulMod(baseValue, modulus);
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
Element1 = current;
|
||||
break;
|
||||
case 2:
|
||||
Element2 = current;
|
||||
break;
|
||||
case 3:
|
||||
Element3 = current;
|
||||
break;
|
||||
case 4:
|
||||
Element4 = current;
|
||||
break;
|
||||
case 5:
|
||||
Element5 = current;
|
||||
break;
|
||||
case 6:
|
||||
Element6 = current;
|
||||
break;
|
||||
case 7:
|
||||
Element7 = current;
|
||||
break;
|
||||
case 8:
|
||||
Element8 = current;
|
||||
break;
|
||||
case 9:
|
||||
Element9 = current;
|
||||
break;
|
||||
case 10:
|
||||
Element10 = current;
|
||||
break;
|
||||
case 11:
|
||||
Element11 = current;
|
||||
break;
|
||||
case 12:
|
||||
Element12 = current;
|
||||
break;
|
||||
case 13:
|
||||
Element13 = current;
|
||||
break;
|
||||
case 14:
|
||||
Element14 = current;
|
||||
break;
|
||||
case 15:
|
||||
Element15 = current;
|
||||
break;
|
||||
case 16:
|
||||
Element16 = current;
|
||||
break;
|
||||
case 17:
|
||||
Element17 = current;
|
||||
break;
|
||||
case 18:
|
||||
Element18 = current;
|
||||
break;
|
||||
case 19:
|
||||
Element19 = current;
|
||||
break;
|
||||
case 20:
|
||||
Element20 = current;
|
||||
break;
|
||||
case 21:
|
||||
Element21 = current;
|
||||
break;
|
||||
case 22:
|
||||
Element22 = current;
|
||||
break;
|
||||
case 23:
|
||||
Element23 = current;
|
||||
break;
|
||||
case 24:
|
||||
Element24 = current;
|
||||
break;
|
||||
case 25:
|
||||
Element25 = current;
|
||||
break;
|
||||
case 26:
|
||||
Element26 = current;
|
||||
break;
|
||||
case 27:
|
||||
Element27 = current;
|
||||
break;
|
||||
case 28:
|
||||
Element28 = current;
|
||||
break;
|
||||
case 29:
|
||||
Element29 = current;
|
||||
break;
|
||||
case 30:
|
||||
Element30 = current;
|
||||
break;
|
||||
case 31:
|
||||
Element31 = current;
|
||||
break;
|
||||
case 32:
|
||||
Element32 = current;
|
||||
break;
|
||||
case 33:
|
||||
Element33 = current;
|
||||
break;
|
||||
case 34:
|
||||
Element34 = current;
|
||||
break;
|
||||
case 35:
|
||||
Element35 = current;
|
||||
break;
|
||||
case 36:
|
||||
Element36 = current;
|
||||
break;
|
||||
case 37:
|
||||
Element37 = current;
|
||||
break;
|
||||
case 38:
|
||||
Element38 = current;
|
||||
break;
|
||||
case 39:
|
||||
Element39 = current;
|
||||
break;
|
||||
case 40:
|
||||
Element40 = current;
|
||||
break;
|
||||
case 41:
|
||||
Element41 = current;
|
||||
break;
|
||||
case 42:
|
||||
Element42 = current;
|
||||
break;
|
||||
case 43:
|
||||
Element43 = current;
|
||||
break;
|
||||
case 44:
|
||||
Element44 = current;
|
||||
break;
|
||||
case 45:
|
||||
Element45 = current;
|
||||
break;
|
||||
case 46:
|
||||
Element46 = current;
|
||||
break;
|
||||
case 47:
|
||||
Element47 = current;
|
||||
break;
|
||||
case 48:
|
||||
Element48 = current;
|
||||
break;
|
||||
case 49:
|
||||
Element49 = current;
|
||||
break;
|
||||
case 50:
|
||||
Element50 = current;
|
||||
break;
|
||||
case 51:
|
||||
Element51 = current;
|
||||
break;
|
||||
case 52:
|
||||
Element52 = current;
|
||||
break;
|
||||
case 53:
|
||||
Element53 = current;
|
||||
break;
|
||||
case 54:
|
||||
Element54 = current;
|
||||
break;
|
||||
case 55:
|
||||
Element55 = current;
|
||||
break;
|
||||
case 56:
|
||||
Element56 = current;
|
||||
break;
|
||||
case 57:
|
||||
Element57 = current;
|
||||
break;
|
||||
case 58:
|
||||
Element58 = current;
|
||||
break;
|
||||
case 59:
|
||||
Element59 = current;
|
||||
break;
|
||||
case 60:
|
||||
Element60 = current;
|
||||
break;
|
||||
case 61:
|
||||
Element61 = current;
|
||||
break;
|
||||
case 62:
|
||||
Element62 = current;
|
||||
break;
|
||||
case 63:
|
||||
Element63 = current;
|
||||
break;
|
||||
case 64:
|
||||
Element64 = current;
|
||||
break;
|
||||
case 65:
|
||||
Element65 = current;
|
||||
break;
|
||||
case 66:
|
||||
Element66 = current;
|
||||
break;
|
||||
case 67:
|
||||
Element67 = current;
|
||||
break;
|
||||
case 68:
|
||||
Element68 = current;
|
||||
break;
|
||||
case 69:
|
||||
Element69 = current;
|
||||
break;
|
||||
case 70:
|
||||
Element70 = current;
|
||||
break;
|
||||
case 71:
|
||||
Element71 = current;
|
||||
break;
|
||||
case 72:
|
||||
Element72 = current;
|
||||
break;
|
||||
case 73:
|
||||
Element73 = current;
|
||||
break;
|
||||
case 74:
|
||||
Element74 = current;
|
||||
break;
|
||||
case 75:
|
||||
Element75 = current;
|
||||
break;
|
||||
case 76:
|
||||
Element76 = current;
|
||||
break;
|
||||
case 77:
|
||||
Element77 = current;
|
||||
break;
|
||||
case 78:
|
||||
Element78 = current;
|
||||
break;
|
||||
case 79:
|
||||
Element79 = current;
|
||||
break;
|
||||
case 80:
|
||||
Element80 = current;
|
||||
break;
|
||||
case 81:
|
||||
Element81 = current;
|
||||
break;
|
||||
case 82:
|
||||
Element82 = current;
|
||||
break;
|
||||
case 83:
|
||||
Element83 = current;
|
||||
break;
|
||||
case 84:
|
||||
Element84 = current;
|
||||
break;
|
||||
case 85:
|
||||
Element85 = current;
|
||||
break;
|
||||
case 86:
|
||||
Element86 = current;
|
||||
break;
|
||||
case 87:
|
||||
Element87 = current;
|
||||
break;
|
||||
case 88:
|
||||
Element88 = current;
|
||||
break;
|
||||
case 89:
|
||||
Element89 = current;
|
||||
break;
|
||||
case 90:
|
||||
Element90 = current;
|
||||
break;
|
||||
case 91:
|
||||
Element91 = current;
|
||||
break;
|
||||
case 92:
|
||||
Element92 = current;
|
||||
break;
|
||||
case 93:
|
||||
Element93 = current;
|
||||
break;
|
||||
case 94:
|
||||
Element94 = current;
|
||||
break;
|
||||
case 95:
|
||||
Element95 = current;
|
||||
break;
|
||||
case 96:
|
||||
Element96 = current;
|
||||
break;
|
||||
case 97:
|
||||
Element97 = current;
|
||||
break;
|
||||
case 98:
|
||||
Element98 = current;
|
||||
break;
|
||||
case 99:
|
||||
Element99 = current;
|
||||
break;
|
||||
case 100:
|
||||
Element100 = current;
|
||||
break;
|
||||
case 101:
|
||||
Element101 = current;
|
||||
break;
|
||||
case 102:
|
||||
Element102 = current;
|
||||
break;
|
||||
case 103:
|
||||
Element103 = current;
|
||||
break;
|
||||
case 104:
|
||||
Element104 = current;
|
||||
break;
|
||||
case 105:
|
||||
Element105 = current;
|
||||
break;
|
||||
case 106:
|
||||
Element106 = current;
|
||||
break;
|
||||
case 107:
|
||||
Element107 = current;
|
||||
break;
|
||||
case 108:
|
||||
Element108 = current;
|
||||
break;
|
||||
case 109:
|
||||
Element109 = current;
|
||||
break;
|
||||
case 110:
|
||||
Element110 = current;
|
||||
break;
|
||||
case 111:
|
||||
Element111 = current;
|
||||
break;
|
||||
case 112:
|
||||
Element112 = current;
|
||||
break;
|
||||
case 113:
|
||||
Element113 = current;
|
||||
break;
|
||||
case 114:
|
||||
Element114 = current;
|
||||
break;
|
||||
case 115:
|
||||
Element115 = current;
|
||||
break;
|
||||
case 116:
|
||||
Element116 = current;
|
||||
break;
|
||||
case 117:
|
||||
Element117 = current;
|
||||
break;
|
||||
case 118:
|
||||
Element118 = current;
|
||||
break;
|
||||
case 119:
|
||||
Element119 = current;
|
||||
break;
|
||||
case 120:
|
||||
Element120 = current;
|
||||
break;
|
||||
case 121:
|
||||
Element121 = current;
|
||||
break;
|
||||
case 122:
|
||||
Element122 = current;
|
||||
break;
|
||||
case 123:
|
||||
Element123 = current;
|
||||
break;
|
||||
case 124:
|
||||
Element124 = current;
|
||||
break;
|
||||
case 125:
|
||||
Element125 = current;
|
||||
break;
|
||||
case 126:
|
||||
Element126 = current;
|
||||
break;
|
||||
case 127:
|
||||
Element127 = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly GpuUInt128 Element0;
|
||||
public readonly GpuUInt128 Element1;
|
||||
public readonly GpuUInt128 Element2;
|
||||
public readonly GpuUInt128 Element3;
|
||||
public readonly GpuUInt128 Element4;
|
||||
public readonly GpuUInt128 Element5;
|
||||
public readonly GpuUInt128 Element6;
|
||||
public readonly GpuUInt128 Element7;
|
||||
public readonly GpuUInt128 Element8;
|
||||
public readonly GpuUInt128 Element9;
|
||||
public readonly GpuUInt128 Element10;
|
||||
public readonly GpuUInt128 Element11;
|
||||
public readonly GpuUInt128 Element12;
|
||||
public readonly GpuUInt128 Element13;
|
||||
public readonly GpuUInt128 Element14;
|
||||
public readonly GpuUInt128 Element15;
|
||||
public readonly GpuUInt128 Element16;
|
||||
public readonly GpuUInt128 Element17;
|
||||
public readonly GpuUInt128 Element18;
|
||||
public readonly GpuUInt128 Element19;
|
||||
public readonly GpuUInt128 Element20;
|
||||
public readonly GpuUInt128 Element21;
|
||||
public readonly GpuUInt128 Element22;
|
||||
public readonly GpuUInt128 Element23;
|
||||
public readonly GpuUInt128 Element24;
|
||||
public readonly GpuUInt128 Element25;
|
||||
public readonly GpuUInt128 Element26;
|
||||
public readonly GpuUInt128 Element27;
|
||||
public readonly GpuUInt128 Element28;
|
||||
public readonly GpuUInt128 Element29;
|
||||
public readonly GpuUInt128 Element30;
|
||||
public readonly GpuUInt128 Element31;
|
||||
public readonly GpuUInt128 Element32;
|
||||
public readonly GpuUInt128 Element33;
|
||||
public readonly GpuUInt128 Element34;
|
||||
public readonly GpuUInt128 Element35;
|
||||
public readonly GpuUInt128 Element36;
|
||||
public readonly GpuUInt128 Element37;
|
||||
public readonly GpuUInt128 Element38;
|
||||
public readonly GpuUInt128 Element39;
|
||||
public readonly GpuUInt128 Element40;
|
||||
public readonly GpuUInt128 Element41;
|
||||
public readonly GpuUInt128 Element42;
|
||||
public readonly GpuUInt128 Element43;
|
||||
public readonly GpuUInt128 Element44;
|
||||
public readonly GpuUInt128 Element45;
|
||||
public readonly GpuUInt128 Element46;
|
||||
public readonly GpuUInt128 Element47;
|
||||
public readonly GpuUInt128 Element48;
|
||||
public readonly GpuUInt128 Element49;
|
||||
public readonly GpuUInt128 Element50;
|
||||
public readonly GpuUInt128 Element51;
|
||||
public readonly GpuUInt128 Element52;
|
||||
public readonly GpuUInt128 Element53;
|
||||
public readonly GpuUInt128 Element54;
|
||||
public readonly GpuUInt128 Element55;
|
||||
public readonly GpuUInt128 Element56;
|
||||
public readonly GpuUInt128 Element57;
|
||||
public readonly GpuUInt128 Element58;
|
||||
public readonly GpuUInt128 Element59;
|
||||
public readonly GpuUInt128 Element60;
|
||||
public readonly GpuUInt128 Element61;
|
||||
public readonly GpuUInt128 Element62;
|
||||
public readonly GpuUInt128 Element63;
|
||||
public readonly GpuUInt128 Element64;
|
||||
public readonly GpuUInt128 Element65;
|
||||
public readonly GpuUInt128 Element66;
|
||||
public readonly GpuUInt128 Element67;
|
||||
public readonly GpuUInt128 Element68;
|
||||
public readonly GpuUInt128 Element69;
|
||||
public readonly GpuUInt128 Element70;
|
||||
public readonly GpuUInt128 Element71;
|
||||
public readonly GpuUInt128 Element72;
|
||||
public readonly GpuUInt128 Element73;
|
||||
public readonly GpuUInt128 Element74;
|
||||
public readonly GpuUInt128 Element75;
|
||||
public readonly GpuUInt128 Element76;
|
||||
public readonly GpuUInt128 Element77;
|
||||
public readonly GpuUInt128 Element78;
|
||||
public readonly GpuUInt128 Element79;
|
||||
public readonly GpuUInt128 Element80;
|
||||
public readonly GpuUInt128 Element81;
|
||||
public readonly GpuUInt128 Element82;
|
||||
public readonly GpuUInt128 Element83;
|
||||
public readonly GpuUInt128 Element84;
|
||||
public readonly GpuUInt128 Element85;
|
||||
public readonly GpuUInt128 Element86;
|
||||
public readonly GpuUInt128 Element87;
|
||||
public readonly GpuUInt128 Element88;
|
||||
public readonly GpuUInt128 Element89;
|
||||
public readonly GpuUInt128 Element90;
|
||||
public readonly GpuUInt128 Element91;
|
||||
public readonly GpuUInt128 Element92;
|
||||
public readonly GpuUInt128 Element93;
|
||||
public readonly GpuUInt128 Element94;
|
||||
public readonly GpuUInt128 Element95;
|
||||
public readonly GpuUInt128 Element96;
|
||||
public readonly GpuUInt128 Element97;
|
||||
public readonly GpuUInt128 Element98;
|
||||
public readonly GpuUInt128 Element99;
|
||||
public readonly GpuUInt128 Element100;
|
||||
public readonly GpuUInt128 Element101;
|
||||
public readonly GpuUInt128 Element102;
|
||||
public readonly GpuUInt128 Element103;
|
||||
public readonly GpuUInt128 Element104;
|
||||
public readonly GpuUInt128 Element105;
|
||||
public readonly GpuUInt128 Element106;
|
||||
public readonly GpuUInt128 Element107;
|
||||
public readonly GpuUInt128 Element108;
|
||||
public readonly GpuUInt128 Element109;
|
||||
public readonly GpuUInt128 Element110;
|
||||
public readonly GpuUInt128 Element111;
|
||||
public readonly GpuUInt128 Element112;
|
||||
public readonly GpuUInt128 Element113;
|
||||
public readonly GpuUInt128 Element114;
|
||||
public readonly GpuUInt128 Element115;
|
||||
public readonly GpuUInt128 Element116;
|
||||
public readonly GpuUInt128 Element117;
|
||||
public readonly GpuUInt128 Element118;
|
||||
public readonly GpuUInt128 Element119;
|
||||
public readonly GpuUInt128 Element120;
|
||||
public readonly GpuUInt128 Element121;
|
||||
public readonly GpuUInt128 Element122;
|
||||
public readonly GpuUInt128 Element123;
|
||||
public readonly GpuUInt128 Element124;
|
||||
public readonly GpuUInt128 Element125;
|
||||
public readonly GpuUInt128 Element126;
|
||||
public readonly GpuUInt128 Element127;
|
||||
|
||||
public readonly GpuUInt128 this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return index switch
|
||||
{
|
||||
0 => Element0,
|
||||
1 => Element1,
|
||||
2 => Element2,
|
||||
3 => Element3,
|
||||
4 => Element4,
|
||||
5 => Element5,
|
||||
6 => Element6,
|
||||
7 => Element7,
|
||||
8 => Element8,
|
||||
9 => Element9,
|
||||
10 => Element10,
|
||||
11 => Element11,
|
||||
12 => Element12,
|
||||
13 => Element13,
|
||||
14 => Element14,
|
||||
15 => Element15,
|
||||
16 => Element16,
|
||||
17 => Element17,
|
||||
18 => Element18,
|
||||
19 => Element19,
|
||||
20 => Element20,
|
||||
21 => Element21,
|
||||
22 => Element22,
|
||||
23 => Element23,
|
||||
24 => Element24,
|
||||
25 => Element25,
|
||||
26 => Element26,
|
||||
27 => Element27,
|
||||
28 => Element28,
|
||||
29 => Element29,
|
||||
30 => Element30,
|
||||
31 => Element31,
|
||||
32 => Element32,
|
||||
33 => Element33,
|
||||
34 => Element34,
|
||||
35 => Element35,
|
||||
36 => Element36,
|
||||
37 => Element37,
|
||||
38 => Element38,
|
||||
39 => Element39,
|
||||
40 => Element40,
|
||||
41 => Element41,
|
||||
42 => Element42,
|
||||
43 => Element43,
|
||||
44 => Element44,
|
||||
45 => Element45,
|
||||
46 => Element46,
|
||||
47 => Element47,
|
||||
48 => Element48,
|
||||
49 => Element49,
|
||||
50 => Element50,
|
||||
51 => Element51,
|
||||
52 => Element52,
|
||||
53 => Element53,
|
||||
54 => Element54,
|
||||
55 => Element55,
|
||||
56 => Element56,
|
||||
57 => Element57,
|
||||
58 => Element58,
|
||||
59 => Element59,
|
||||
60 => Element60,
|
||||
61 => Element61,
|
||||
62 => Element62,
|
||||
63 => Element63,
|
||||
64 => Element64,
|
||||
65 => Element65,
|
||||
66 => Element66,
|
||||
67 => Element67,
|
||||
68 => Element68,
|
||||
69 => Element69,
|
||||
70 => Element70,
|
||||
71 => Element71,
|
||||
72 => Element72,
|
||||
73 => Element73,
|
||||
74 => Element74,
|
||||
75 => Element75,
|
||||
76 => Element76,
|
||||
77 => Element77,
|
||||
78 => Element78,
|
||||
79 => Element79,
|
||||
80 => Element80,
|
||||
81 => Element81,
|
||||
82 => Element82,
|
||||
83 => Element83,
|
||||
84 => Element84,
|
||||
85 => Element85,
|
||||
86 => Element86,
|
||||
87 => Element87,
|
||||
88 => Element88,
|
||||
89 => Element89,
|
||||
90 => Element90,
|
||||
91 => Element91,
|
||||
92 => Element92,
|
||||
93 => Element93,
|
||||
94 => Element94,
|
||||
95 => Element95,
|
||||
96 => Element96,
|
||||
97 => Element97,
|
||||
98 => Element98,
|
||||
99 => Element99,
|
||||
100 => Element100,
|
||||
101 => Element101,
|
||||
102 => Element102,
|
||||
103 => Element103,
|
||||
104 => Element104,
|
||||
105 => Element105,
|
||||
106 => Element106,
|
||||
107 => Element107,
|
||||
108 => Element108,
|
||||
109 => Element109,
|
||||
110 => Element110,
|
||||
111 => Element111,
|
||||
112 => Element112,
|
||||
113 => Element113,
|
||||
114 => Element114,
|
||||
115 => Element115,
|
||||
116 => Element116,
|
||||
117 => Element117,
|
||||
118 => Element118,
|
||||
119 => Element119,
|
||||
120 => Element120,
|
||||
121 => Element121,
|
||||
122 => Element122,
|
||||
123 => Element123,
|
||||
124 => Element124,
|
||||
125 => Element125,
|
||||
126 => Element126,
|
||||
127 => Element127,
|
||||
_ => Element0,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU.Runtime.OpenCL;
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
internal enum GpuPow2ModStatus
|
||||
@@ -26,73 +27,37 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
private static readonly ConcurrentDictionary<Accelerator, OrderKernelLauncher> OrderKernelCache = new();
|
||||
private static readonly ConcurrentDictionary<Accelerator, SmallPrimeDeviceCache> SmallPrimeDeviceCaches = new();
|
||||
|
||||
private readonly struct OrderKernelConfig
|
||||
public readonly struct OrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode)
|
||||
{
|
||||
public OrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode)
|
||||
{
|
||||
PreviousOrder = previousOrder;
|
||||
HasPreviousOrder = hasPreviousOrder;
|
||||
SmallFactorLimit = smallFactorLimit;
|
||||
MaxPowChecks = maxPowChecks;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public ulong PreviousOrder { get; }
|
||||
|
||||
public byte HasPreviousOrder { get; }
|
||||
|
||||
public uint SmallFactorLimit { get; }
|
||||
|
||||
public int MaxPowChecks { get; }
|
||||
|
||||
public int Mode { get; }
|
||||
public readonly ulong PreviousOrder = previousOrder;
|
||||
public readonly byte HasPreviousOrder = hasPreviousOrder;
|
||||
public readonly uint SmallFactorLimit = smallFactorLimit;
|
||||
public readonly int MaxPowChecks = maxPowChecks;
|
||||
public readonly int Mode = mode;
|
||||
}
|
||||
|
||||
private readonly struct OrderKernelBuffers
|
||||
public readonly struct OrderKernelBuffers(
|
||||
ArrayView1D<ulong, Stride1D.Dense> phiFactors,
|
||||
ArrayView1D<int, Stride1D.Dense> phiExponents,
|
||||
ArrayView1D<ulong, Stride1D.Dense> workFactors,
|
||||
ArrayView1D<int, Stride1D.Dense> workExponents,
|
||||
ArrayView1D<ulong, Stride1D.Dense> candidates,
|
||||
ArrayView1D<int, Stride1D.Dense> stackIndex,
|
||||
ArrayView1D<int, Stride1D.Dense> stackExponent,
|
||||
ArrayView1D<ulong, Stride1D.Dense> stackProduct,
|
||||
ArrayView1D<ulong, Stride1D.Dense> result,
|
||||
ArrayView1D<byte, Stride1D.Dense> status)
|
||||
{
|
||||
public OrderKernelBuffers(
|
||||
ArrayView1D<ulong, Stride1D.Dense> phiFactors,
|
||||
ArrayView1D<int, Stride1D.Dense> phiExponents,
|
||||
ArrayView1D<ulong, Stride1D.Dense> workFactors,
|
||||
ArrayView1D<int, Stride1D.Dense> workExponents,
|
||||
ArrayView1D<ulong, Stride1D.Dense> candidates,
|
||||
ArrayView1D<int, Stride1D.Dense> stackIndex,
|
||||
ArrayView1D<int, Stride1D.Dense> stackExponent,
|
||||
ArrayView1D<ulong, Stride1D.Dense> stackProduct,
|
||||
ArrayView1D<ulong, Stride1D.Dense> result,
|
||||
ArrayView1D<byte, Stride1D.Dense> status)
|
||||
{
|
||||
PhiFactors = phiFactors;
|
||||
PhiExponents = phiExponents;
|
||||
WorkFactors = workFactors;
|
||||
WorkExponents = workExponents;
|
||||
Candidates = candidates;
|
||||
StackIndex = stackIndex;
|
||||
StackExponent = stackExponent;
|
||||
StackProduct = stackProduct;
|
||||
Result = result;
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> PhiFactors { get; }
|
||||
|
||||
public ArrayView1D<int, Stride1D.Dense> PhiExponents { get; }
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> WorkFactors { get; }
|
||||
|
||||
public ArrayView1D<int, Stride1D.Dense> WorkExponents { get; }
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> Candidates { get; }
|
||||
|
||||
public ArrayView1D<int, Stride1D.Dense> StackIndex { get; }
|
||||
|
||||
public ArrayView1D<int, Stride1D.Dense> StackExponent { get; }
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> StackProduct { get; }
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> Result { get; }
|
||||
|
||||
public ArrayView1D<byte, Stride1D.Dense> Status { get; }
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> PhiFactors = phiFactors;
|
||||
public readonly ArrayView1D<int, Stride1D.Dense> PhiExponents = phiExponents;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> WorkFactors = workFactors;
|
||||
public readonly ArrayView1D<int, Stride1D.Dense> WorkExponents = workExponents;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> Candidates = candidates;
|
||||
public readonly ArrayView1D<int, Stride1D.Dense> StackIndex = stackIndex;
|
||||
public readonly ArrayView1D<int, Stride1D.Dense> StackExponent = stackExponent;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> StackProduct = stackProduct;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> Result = result;
|
||||
public readonly ArrayView1D<byte, Stride1D.Dense> Status = status;
|
||||
}
|
||||
|
||||
private delegate void OrderKernelLauncher(
|
||||
@@ -124,13 +89,6 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
|
||||
private const int GpuSmallPrimeFactorSlots = 64;
|
||||
|
||||
|
||||
[InlineArray(Pow2WindowOddPowerCount)]
|
||||
private struct Pow2OddPowerTable
|
||||
{
|
||||
private GpuUInt128 _element0;
|
||||
}
|
||||
|
||||
internal static ConcurrentDictionary<ulong, byte> OverflowRegistry => OverflowedPrimes;
|
||||
internal static ConcurrentDictionary<UInt128, byte> OverflowRegistryWide => OverflowedPrimesWide;
|
||||
|
||||
@@ -237,63 +195,72 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
try
|
||||
{
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
using var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
|
||||
var kernel = GetPartialFactorKernel(accelerator);
|
||||
SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator);
|
||||
var kernel = GetPartialFactorKernel(accelerator);
|
||||
SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator);
|
||||
|
||||
using var factorBuffer = accelerator.Allocate1D<ulong>(primeTargets.Length);
|
||||
using var exponentBuffer = accelerator.Allocate1D<int>(exponentTargets.Length);
|
||||
using var countBuffer = accelerator.Allocate1D<int>(1);
|
||||
using var remainingBuffer = accelerator.Allocate1D<ulong>(1);
|
||||
using var fullyFactoredBuffer = accelerator.Allocate1D<byte>(1);
|
||||
var factorBuffer = accelerator.Allocate1D<ulong>(primeTargets.Length);
|
||||
var exponentBuffer = accelerator.Allocate1D<int>(exponentTargets.Length);
|
||||
var countBuffer = accelerator.Allocate1D<int>(1);
|
||||
var remainingBuffer = accelerator.Allocate1D<ulong>(1);
|
||||
var fullyFactoredBuffer = accelerator.Allocate1D<byte>(1);
|
||||
|
||||
factorBuffer.MemSetToZero();
|
||||
exponentBuffer.MemSetToZero();
|
||||
countBuffer.MemSetToZero();
|
||||
remainingBuffer.MemSetToZero();
|
||||
fullyFactoredBuffer.MemSetToZero();
|
||||
factorBuffer.MemSetToZero();
|
||||
exponentBuffer.MemSetToZero();
|
||||
countBuffer.MemSetToZero();
|
||||
remainingBuffer.MemSetToZero();
|
||||
fullyFactoredBuffer.MemSetToZero();
|
||||
|
||||
kernel(
|
||||
stream,
|
||||
1,
|
||||
cache.Primes!.View,
|
||||
cache.Squares!.View,
|
||||
cache.Count,
|
||||
primeTargets.Length,
|
||||
value,
|
||||
limit,
|
||||
factorBuffer.View,
|
||||
exponentBuffer.View,
|
||||
countBuffer.View,
|
||||
remainingBuffer.View,
|
||||
fullyFactoredBuffer.View);
|
||||
kernel(
|
||||
stream,
|
||||
1,
|
||||
cache.Primes!.View,
|
||||
cache.Squares!.View,
|
||||
cache.Count,
|
||||
primeTargets.Length,
|
||||
value,
|
||||
limit,
|
||||
factorBuffer.View,
|
||||
exponentBuffer.View,
|
||||
countBuffer.View,
|
||||
remainingBuffer.View,
|
||||
fullyFactoredBuffer.View);
|
||||
|
||||
stream.Synchronize();
|
||||
stream.Synchronize();
|
||||
|
||||
countBuffer.View.CopyToCPU(ref factorCount, 1);
|
||||
factorCount = Math.Min(factorCount, primeTargets.Length);
|
||||
factorBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(primeTargets), primeTargets.Length);
|
||||
exponentBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentTargets), exponentTargets.Length);
|
||||
remainingBuffer.View.CopyToCPU(ref remaining, 1);
|
||||
countBuffer.View.CopyToCPU(ref factorCount, 1);
|
||||
factorCount = Math.Min(factorCount, primeTargets.Length);
|
||||
factorBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(primeTargets), primeTargets.Length);
|
||||
exponentBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentTargets), exponentTargets.Length);
|
||||
remainingBuffer.View.CopyToCPU(ref remaining, 1);
|
||||
|
||||
byte fullyFactoredFlag = 0;
|
||||
fullyFactoredBuffer.View.CopyToCPU(ref fullyFactoredFlag, 1);
|
||||
fullyFactored = fullyFactoredFlag != 0;
|
||||
byte fullyFactoredFlag = 0;
|
||||
fullyFactoredBuffer.View.CopyToCPU(ref fullyFactoredFlag, 1);
|
||||
fullyFactored = fullyFactoredFlag != 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
factorBuffer.Dispose();
|
||||
exponentBuffer.Dispose();
|
||||
countBuffer.Dispose();
|
||||
remainingBuffer.Dispose();
|
||||
fullyFactoredBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
lease.Dispose();
|
||||
return true;
|
||||
}
|
||||
catch (CLException ex)
|
||||
{
|
||||
Console.WriteLine($"GPU ERROR ({ex.Error}): {ex.Message}");
|
||||
factorCount = 0;
|
||||
remaining = value;
|
||||
fullyFactored = false;
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
|
||||
{
|
||||
Console.WriteLine($"GPU ERROR: {ex.Message}");
|
||||
factorCount = 0;
|
||||
remaining = value;
|
||||
fullyFactored = false;
|
||||
@@ -361,36 +328,44 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
remainingOut[0] = remainingLocal;
|
||||
fullyFactoredOut[0] = remainingLocal == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder)
|
||||
public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
Span<ulong> exponents = stackalloc ulong[1];
|
||||
Span<ulong> remainders = stackalloc ulong[1];
|
||||
exponents[0] = exponent;
|
||||
|
||||
GpuPow2ModStatus status = TryPow2ModBatch(exponents, prime, remainders);
|
||||
GpuPow2ModStatus status = TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
remainder = remainders[0];
|
||||
return status;
|
||||
}
|
||||
|
||||
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> remainders)
|
||||
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> remainders, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
if (exponents.Length == 0)
|
||||
{
|
||||
return GpuPow2ModStatus.Success;
|
||||
}
|
||||
// This will never occur in production code
|
||||
// if (exponents.Length == 0)
|
||||
// {
|
||||
// return GpuPow2ModStatus.Success;
|
||||
// }
|
||||
|
||||
if (remainders.Length < exponents.Length)
|
||||
{
|
||||
throw new ArgumentException("Remainder span is shorter than the exponent span.", nameof(remainders));
|
||||
}
|
||||
// This will never occur in production code
|
||||
// if (remainders.Length < exponents.Length)
|
||||
// {
|
||||
// throw new ArgumentException("Remainder span is shorter than the exponent span.", nameof(remainders));
|
||||
// }
|
||||
|
||||
Span<ulong> target = remainders.Slice(0, exponents.Length);
|
||||
target.Clear();
|
||||
// This will never occur in production code
|
||||
// if (remainders.Length > exponents.Length)
|
||||
// throw new ArgumentException("Remainder span is longer than the exponent span.", nameof(remainders));
|
||||
|
||||
if (prime <= 1UL)
|
||||
{
|
||||
return GpuPow2ModStatus.Unavailable;
|
||||
}
|
||||
// We need to clear more, but we save on allocations
|
||||
// Span<ulong> target = remainders[..exponents.Length];
|
||||
remainders.Clear();
|
||||
|
||||
// This will never occur in production code
|
||||
// if (prime <= 1UL)
|
||||
// {
|
||||
// return GpuPow2ModStatus.Unavailable;
|
||||
// }
|
||||
|
||||
ConcurrentDictionary<ulong, byte> overflowRegistry = OverflowedPrimes;
|
||||
|
||||
@@ -415,11 +390,11 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
}
|
||||
|
||||
bool computed = TryComputeOnGpu(exponents, prime, target);
|
||||
bool computed = TryComputeOnGpu(exponents, prime, divisorData, remainders);
|
||||
return computed ? GpuPow2ModStatus.Success : GpuPow2ModStatus.Unavailable;
|
||||
}
|
||||
|
||||
public static GpuPow2ModStatus TryPow2Mod(UInt128 exponent, UInt128 prime, out UInt128 remainder)
|
||||
public static GpuPow2ModStatus TryPow2Mod(in UInt128 exponent, in UInt128 prime, out UInt128 remainder)
|
||||
{
|
||||
Span<UInt128> exponents = stackalloc UInt128[1];
|
||||
Span<UInt128> remainders = stackalloc UInt128[1];
|
||||
@@ -440,108 +415,114 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
ulong? previousOrder,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig config,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out PrimeOrderCalculator.PrimeOrderResult result)
|
||||
out ulong order)
|
||||
{
|
||||
result = default;
|
||||
order = 0UL;
|
||||
|
||||
try
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
|
||||
var kernel = GetOrderKernel(accelerator);
|
||||
SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator);
|
||||
|
||||
var phiFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
|
||||
var phiExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
|
||||
var workFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
|
||||
var workExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
|
||||
var candidateBuffer = accelerator.Allocate1D<ulong>(HeuristicCandidateLimit);
|
||||
var stackIndexBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
|
||||
var stackExponentBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
|
||||
var stackProductBuffer = accelerator.Allocate1D<ulong>(HeuristicStackCapacity);
|
||||
var resultBuffer = accelerator.Allocate1D<ulong>(1);
|
||||
var statusBuffer = accelerator.Allocate1D<byte>(1);
|
||||
|
||||
phiFactorBuffer.MemSetToZero();
|
||||
phiExponentBuffer.MemSetToZero();
|
||||
workFactorBuffer.MemSetToZero();
|
||||
workExponentBuffer.MemSetToZero();
|
||||
candidateBuffer.MemSetToZero();
|
||||
stackIndexBuffer.MemSetToZero();
|
||||
stackExponentBuffer.MemSetToZero();
|
||||
stackProductBuffer.MemSetToZero();
|
||||
resultBuffer.MemSetToZero();
|
||||
statusBuffer.MemSetToZero();
|
||||
|
||||
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
|
||||
byte hasPrevious = previousOrder.HasValue ? (byte)1 : (byte)0;
|
||||
ulong previousValue = previousOrder ?? 0UL;
|
||||
|
||||
var kernelConfig = new OrderKernelConfig(previousValue, hasPrevious, limit, config.MaxPowChecks, (int)config.Mode);
|
||||
var buffers = new OrderKernelBuffers(
|
||||
phiFactorBuffer.View,
|
||||
phiExponentBuffer.View,
|
||||
workFactorBuffer.View,
|
||||
workExponentBuffer.View,
|
||||
candidateBuffer.View,
|
||||
stackIndexBuffer.View,
|
||||
stackExponentBuffer.View,
|
||||
stackProductBuffer.View,
|
||||
resultBuffer.View,
|
||||
statusBuffer.View);
|
||||
|
||||
kernel(
|
||||
stream,
|
||||
1,
|
||||
prime,
|
||||
kernelConfig,
|
||||
divisorData,
|
||||
cache.Primes!.View,
|
||||
cache.Squares!.View,
|
||||
cache.Count,
|
||||
buffers);
|
||||
|
||||
stream.Synchronize();
|
||||
|
||||
byte status = 0;
|
||||
statusBuffer.View.CopyToCPU(ref status, 1);
|
||||
|
||||
PrimeOrderKernelStatus kernelStatus = (PrimeOrderKernelStatus)status;
|
||||
if (kernelStatus == PrimeOrderKernelStatus.Fallback)
|
||||
{
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
using var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
|
||||
var kernel = GetOrderKernel(accelerator);
|
||||
SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator);
|
||||
|
||||
using var phiFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
|
||||
using var phiExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
|
||||
using var workFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
|
||||
using var workExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
|
||||
using var candidateBuffer = accelerator.Allocate1D<ulong>(HeuristicCandidateLimit);
|
||||
using var stackIndexBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
|
||||
using var stackExponentBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
|
||||
using var stackProductBuffer = accelerator.Allocate1D<ulong>(HeuristicStackCapacity);
|
||||
using var resultBuffer = accelerator.Allocate1D<ulong>(1);
|
||||
using var statusBuffer = accelerator.Allocate1D<byte>(1);
|
||||
|
||||
phiFactorBuffer.MemSetToZero();
|
||||
phiExponentBuffer.MemSetToZero();
|
||||
workFactorBuffer.MemSetToZero();
|
||||
workExponentBuffer.MemSetToZero();
|
||||
candidateBuffer.MemSetToZero();
|
||||
stackIndexBuffer.MemSetToZero();
|
||||
stackExponentBuffer.MemSetToZero();
|
||||
stackProductBuffer.MemSetToZero();
|
||||
resultBuffer.MemSetToZero();
|
||||
statusBuffer.MemSetToZero();
|
||||
|
||||
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
|
||||
byte hasPrevious = previousOrder.HasValue ? (byte)1 : (byte)0;
|
||||
ulong previousValue = previousOrder ?? 0UL;
|
||||
|
||||
var kernelConfig = new OrderKernelConfig(previousValue, hasPrevious, limit, config.MaxPowChecks, (int)config.Mode);
|
||||
var buffers = new OrderKernelBuffers(
|
||||
phiFactorBuffer.View,
|
||||
phiExponentBuffer.View,
|
||||
workFactorBuffer.View,
|
||||
workExponentBuffer.View,
|
||||
candidateBuffer.View,
|
||||
stackIndexBuffer.View,
|
||||
stackExponentBuffer.View,
|
||||
stackProductBuffer.View,
|
||||
resultBuffer.View,
|
||||
statusBuffer.View);
|
||||
|
||||
kernel(
|
||||
stream,
|
||||
1,
|
||||
prime,
|
||||
kernelConfig,
|
||||
divisorData,
|
||||
cache.Primes!.View,
|
||||
cache.Squares!.View,
|
||||
cache.Count,
|
||||
buffers);
|
||||
|
||||
stream.Synchronize();
|
||||
|
||||
byte status = 0;
|
||||
statusBuffer.View.CopyToCPU(ref status, 1);
|
||||
|
||||
PrimeOrderKernelStatus kernelStatus = (PrimeOrderKernelStatus)status;
|
||||
if (kernelStatus == PrimeOrderKernelStatus.Fallback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kernelStatus == PrimeOrderKernelStatus.PollardOverflow)
|
||||
{
|
||||
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
||||
}
|
||||
|
||||
ulong order = 0UL;
|
||||
resultBuffer.View.CopyToCPU(ref order, 1);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderStatus finalStatus = kernelStatus == PrimeOrderKernelStatus.Found
|
||||
? PrimeOrderCalculator.PrimeOrderStatus.Found
|
||||
: PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved;
|
||||
|
||||
result = new PrimeOrderCalculator.PrimeOrderResult(finalStatus, order);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
|
||||
{
|
||||
result = default;
|
||||
DisposeResources();
|
||||
lease.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kernelStatus == PrimeOrderKernelStatus.PollardOverflow)
|
||||
{
|
||||
DisposeResources();
|
||||
lease.Dispose();
|
||||
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
||||
}
|
||||
|
||||
resultBuffer.View.CopyToCPU(ref order, 1);
|
||||
|
||||
if (kernelStatus == PrimeOrderKernelStatus.FactoringFailure)
|
||||
{
|
||||
order = 0UL;
|
||||
}
|
||||
|
||||
DisposeResources();
|
||||
lease.Dispose();
|
||||
|
||||
return order != 0UL;
|
||||
|
||||
void DisposeResources()
|
||||
{
|
||||
phiFactorBuffer.Dispose();
|
||||
phiExponentBuffer.Dispose();
|
||||
workFactorBuffer.Dispose();
|
||||
workExponentBuffer.Dispose();
|
||||
candidateBuffer.Dispose();
|
||||
stackIndexBuffer.Dispose();
|
||||
stackExponentBuffer.Dispose();
|
||||
stackProductBuffer.Dispose();
|
||||
resultBuffer.Dispose();
|
||||
statusBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static OrderKernelLauncher GetOrderKernel(Accelerator accelerator)
|
||||
@@ -830,7 +811,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
ulong reduced = phi / factor;
|
||||
if (Pow2EqualsOneKernel(reduced, prime, divisor))
|
||||
if (reduced.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -846,7 +827,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
if (residue == 1UL || residue == 7UL)
|
||||
{
|
||||
ulong half = phi >> 1;
|
||||
if (Pow2EqualsOneKernel(half, prime, divisor))
|
||||
if (Pow2EqualsOneKernel(half, divisor))
|
||||
{
|
||||
order = half;
|
||||
}
|
||||
@@ -880,7 +861,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
ulong reduced = order / primeFactor;
|
||||
if (Pow2EqualsOneKernel(reduced, prime, divisor))
|
||||
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||
{
|
||||
order = reduced;
|
||||
continue;
|
||||
@@ -911,7 +892,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Pow2EqualsOneKernel(order, prime, divisor))
|
||||
if (!Pow2EqualsOneKernel(order, divisor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -955,7 +936,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
reduced /= primeFactor;
|
||||
if (Pow2EqualsOneKernel(reduced, prime, divisor))
|
||||
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1046,7 +1027,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
powUsed++;
|
||||
if (!Pow2EqualsOneKernel(candidate, prime, divisor))
|
||||
if (!Pow2EqualsOneKernel(candidate, divisor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1346,7 +1327,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
powUsed++;
|
||||
if (Pow2EqualsOneKernel(reduced, prime, divisor))
|
||||
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1612,9 +1593,9 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
}
|
||||
|
||||
private static bool Pow2EqualsOneKernel(ulong exponent, ulong prime, in MontgomeryDivisorData divisor)
|
||||
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL;
|
||||
return exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL;
|
||||
}
|
||||
|
||||
private static ulong CalculateByDoublingKernel(ulong prime)
|
||||
@@ -1678,49 +1659,48 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return computed ? GpuPow2ModStatus.Success : GpuPow2ModStatus.Unavailable;
|
||||
}
|
||||
|
||||
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> results)
|
||||
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
|
||||
{
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
var kernel = GetPow2ModKernel(accelerator);
|
||||
var exponentBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
|
||||
var remainderBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
|
||||
|
||||
exponentBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(exponents), exponents.Length);
|
||||
remainderBuffer.MemSetToZero();
|
||||
|
||||
try
|
||||
{
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
using var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
var kernel = GetPow2ModKernel(accelerator);
|
||||
using var exponentBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
|
||||
using var remainderBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
|
||||
|
||||
exponentBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(exponents), exponents.Length);
|
||||
remainderBuffer.MemSetToZero();
|
||||
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorDataCache.Get(prime);
|
||||
kernel(stream, exponents.Length, exponentBuffer.View, divisor, remainderBuffer.View);
|
||||
|
||||
stream.Synchronize();
|
||||
|
||||
remainderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), results.Length);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
kernel(stream, exponents.Length, exponentBuffer.View, divisorData, remainderBuffer.View);
|
||||
stream.Synchronize();
|
||||
}
|
||||
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
|
||||
catch (Exception)
|
||||
{
|
||||
ComputePow2ModCpu(exponents, prime, results);
|
||||
return true;
|
||||
Console.WriteLine($"Exception for {prime} and exponents: {string.Join(",", exponents.ToArray())}.");
|
||||
exponentBuffer.Dispose();
|
||||
remainderBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
lease.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
remainderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), exponents.Length);
|
||||
exponentBuffer.Dispose();
|
||||
remainderBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
lease.Dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ComputePow2ModCpu(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> results)
|
||||
private static void ComputePow2ModCpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
|
||||
{
|
||||
int length = exponents.Length;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
results[i] = Pow2ModCpu(exponents[i], prime);
|
||||
results[i] = Pow2ModCpu(exponents[i], prime, divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1738,12 +1718,12 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
|
||||
try
|
||||
{
|
||||
using var execution = lease.EnterExecutionScope();
|
||||
var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
var kernel = GetPow2ModWideKernel(accelerator);
|
||||
using var exponentBuffer = accelerator.Allocate1D<GpuUInt128>(length);
|
||||
using var remainderBuffer = accelerator.Allocate1D<GpuUInt128>(length);
|
||||
var exponentBuffer = accelerator.Allocate1D<GpuUInt128>(length);
|
||||
var remainderBuffer = accelerator.Allocate1D<GpuUInt128>(length);
|
||||
|
||||
Span<GpuUInt128> exponentSpan = length <= WideStackThreshold
|
||||
? stackalloc GpuUInt128[length]
|
||||
@@ -1773,12 +1753,15 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
results[i] = (UInt128)resultSpan[i];
|
||||
}
|
||||
|
||||
exponentBuffer.Dispose();
|
||||
remainderBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
|
||||
catch (Exception)
|
||||
{
|
||||
ComputePow2ModCpuWide(exponents, prime, results);
|
||||
return true;
|
||||
Console.WriteLine($"Exception when computing {prime} with exponents: {string.Join(", ", exponents.ToArray())}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1805,15 +1788,14 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong Pow2ModCpu(ulong exponent, ulong modulus)
|
||||
private static ulong Pow2ModCpu(ulong exponent, ulong modulus, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
if (modulus <= 1UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorDataCache.Get(modulus);
|
||||
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false);
|
||||
return exponent.Pow2MontgomeryModWindowedGpu(divisorData, keepMontgomery: false);
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
||||
@@ -1829,7 +1811,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] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false);
|
||||
remainders[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false);
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
||||
@@ -1848,39 +1830,68 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
remainders[index] = Pow2ModKernelCore(exponent, modulus);
|
||||
}
|
||||
|
||||
private static GpuUInt128[] InitializeOddPowersTable(GpuUInt128 baseValue, GpuUInt128 modulus, int oddPowerCount)
|
||||
{
|
||||
GpuUInt128[] result = new GpuUInt128[PerfectNumberConstants.MaxOddPowersCount];
|
||||
result[0] = baseValue;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reusing baseValue to hold base^2 for the shared odd-power ladder that follows.
|
||||
baseValue.MulMod(baseValue, modulus);
|
||||
|
||||
// TODO: We can calculate baseValue % modulus before loop and use it to increase ladderEntry calculation speed - we'll reuse the base for incremental calculations.
|
||||
GpuUInt128 current = baseValue;
|
||||
|
||||
// We're manually assigning each field to prevent the compiler to initialize each field twice due to auto-initialization. We're using the action to lower the code base size.
|
||||
for (int i = 1; i < oddPowerCount; i++)
|
||||
{
|
||||
current.MulMod(baseValue, modulus);
|
||||
result[i] = current;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static GpuUInt128 Pow2ModKernelCore(GpuUInt128 exponent, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus == GpuUInt128.One)
|
||||
{
|
||||
return GpuUInt128.Zero;
|
||||
}
|
||||
// This should never happen in production code.
|
||||
// if (modulus == GpuUInt128.One)
|
||||
// {
|
||||
// return GpuUInt128.Zero;
|
||||
// }
|
||||
|
||||
if (exponent.IsZero)
|
||||
{
|
||||
return GpuUInt128.One;
|
||||
}
|
||||
// This should never happen in production code.
|
||||
// if (exponent.IsZero)
|
||||
// {
|
||||
// return GpuUInt128.One;
|
||||
// }
|
||||
|
||||
GpuUInt128 baseValue = new GpuUInt128(2UL);
|
||||
if (baseValue.CompareTo(modulus) >= 0)
|
||||
{
|
||||
baseValue.Sub(modulus);
|
||||
}
|
||||
GpuUInt128 baseValue = UInt128Numbers.TwoGpu;
|
||||
|
||||
// This should never happen in production code - 2 should never be greater or equal modulus.
|
||||
// if (baseValue.CompareTo(modulus) >= 0)
|
||||
// {
|
||||
// baseValue.Sub(modulus);
|
||||
// }
|
||||
|
||||
if (ShouldUseSingleBit(exponent))
|
||||
{
|
||||
return Pow2MontgomeryModSingleBit(exponent, modulus, baseValue);
|
||||
}
|
||||
|
||||
int bitLength = exponent.GetBitLength();
|
||||
int windowSize = GetWindowSize(bitLength);
|
||||
int index = exponent.GetBitLength();
|
||||
int windowSize = GetWindowSize(index);
|
||||
index--;
|
||||
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
|
||||
Pow2OddPowerTable oddPowers = default;
|
||||
InitializeOddPowers(ref oddPowers, baseValue, modulus, oddPowerCount);
|
||||
|
||||
GpuUInt128[] oddPowers = InitializeOddPowersTable(baseValue, modulus, oddPowerCount);
|
||||
GpuUInt128 result = GpuUInt128.One;
|
||||
int index = bitLength - 1;
|
||||
|
||||
int windowStart;
|
||||
ulong windowValue;
|
||||
while (index >= 0)
|
||||
{
|
||||
if (!IsBitSet(exponent, index))
|
||||
@@ -1890,7 +1901,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
continue;
|
||||
}
|
||||
|
||||
int windowStart = index - windowSize + 1;
|
||||
windowStart = index - windowSize + 1;
|
||||
if (windowStart < 0)
|
||||
{
|
||||
windowStart = 0;
|
||||
@@ -1901,24 +1912,27 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
windowStart++;
|
||||
}
|
||||
|
||||
int windowBitCount = index - windowStart + 1;
|
||||
for (int square = 0; square < windowBitCount; square++)
|
||||
// We're reusing oddPowerCount as windowBitCount here to lower registry pressure & avoid additional allocation
|
||||
oddPowerCount = index - windowStart + 1;
|
||||
index = windowStart - 1;
|
||||
windowValue = ExtractWindowValue(exponent, windowStart, oddPowerCount);
|
||||
|
||||
// We're reusing windowStart as square here to lower registry pressure & avoid additional allocation
|
||||
for (windowStart = 0; windowStart < oddPowerCount; windowStart++)
|
||||
{
|
||||
result.MulMod(result, modulus);
|
||||
}
|
||||
|
||||
ulong windowValue = ExtractWindowValue(exponent, windowStart, windowBitCount);
|
||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||
GpuUInt128 factor = oddPowers[tableIndex];
|
||||
result.MulMod(factor, modulus);
|
||||
|
||||
index = windowStart - 1;
|
||||
// We're reusing windowStart as tableIndex here to lower registry pressure & avoid additional allocation
|
||||
windowStart = (int)((windowValue - 1UL) >> 1);
|
||||
result.MulMod(oddPowers[windowStart], modulus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool ShouldUseSingleBit(in GpuUInt128 exponent) => exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool ShouldUseSingleBit(GpuUInt128 exponent) => exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold;
|
||||
|
||||
private static int GetWindowSize(int bitLength)
|
||||
{
|
||||
@@ -1950,23 +1964,6 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return Pow2WindowSizeBits;
|
||||
}
|
||||
|
||||
private static void InitializeOddPowers(ref Pow2OddPowerTable oddPowers, GpuUInt128 baseValue, GpuUInt128 modulus, int oddPowerCount)
|
||||
{
|
||||
oddPowers[0] = baseValue;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reusing baseValue to hold base^2 for the shared odd-power ladder that follows.
|
||||
baseValue.MulMod(baseValue, modulus);
|
||||
for (int i = 1; i < oddPowerCount; i++)
|
||||
{
|
||||
oddPowers[i] = oddPowers[i - 1];
|
||||
oddPowers[i].MulMod(baseValue, modulus);
|
||||
}
|
||||
}
|
||||
|
||||
private static GpuUInt128 Pow2MontgomeryModSingleBit(GpuUInt128 exponent, GpuUInt128 modulus, GpuUInt128 baseValue)
|
||||
{
|
||||
GpuUInt128 result = GpuUInt128.One;
|
||||
@@ -1991,7 +1988,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
||||
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||
{
|
||||
if (bitIndex >= 64)
|
||||
{
|
||||
@@ -2001,14 +1998,13 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
||||
}
|
||||
|
||||
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
{
|
||||
if (windowStart != 0)
|
||||
{
|
||||
GpuUInt128 shifted = exponent;
|
||||
shifted.ShiftRight(windowStart);
|
||||
exponent.ShiftRight(windowStart);
|
||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
||||
return shifted.Low & mask;
|
||||
return exponent.Low & mask;
|
||||
}
|
||||
|
||||
ulong directMask = (1UL << windowBitCount) - 1UL;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public readonly struct ReadOnlyGpuUInt128 : IEquatable<ReadOnlyGpuUInt128>
|
||||
{
|
||||
public readonly ulong High;
|
||||
public readonly ulong Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128(ulong high, ulong low)
|
||||
{
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128(ulong low)
|
||||
: this(0UL, low)
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128(UInt128 value)
|
||||
{
|
||||
High = (ulong)(value >> 64);
|
||||
Low = (ulong)value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128(GpuUInt128 value)
|
||||
{
|
||||
High = value.High;
|
||||
Low = value.Low;
|
||||
}
|
||||
|
||||
public bool IsZero
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => High == 0UL && Low == 0UL;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int GetBitLength()
|
||||
{
|
||||
if (High != 0UL)
|
||||
{
|
||||
return 64 + (64 - BitOperations.LeadingZeroCount(High));
|
||||
}
|
||||
|
||||
if (Low == 0UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 64 - BitOperations.LeadingZeroCount(Low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 ToMutable() => new(High, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ReadOnlyGpuUInt128 other) => High == other.High && Low == other.Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object? obj) => obj is ReadOnlyGpuUInt128 other && Equals(other);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(High, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlyGpuUInt128(ulong value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlyGpuUInt128(UInt128 value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlyGpuUInt128(GpuUInt128 value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator UInt128(ReadOnlyGpuUInt128 value) => ((UInt128)value.High << 64) | value.Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128 WithHigh(ulong high) => new(high, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128 WithLow(ulong low) => new(High, low);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace PerfectNumbers.Core
|
||||
{
|
||||
internal static class HeuristicFailureLog
|
||||
{
|
||||
private const string LogFileName = "prime-order-heuristic-fallbacks.log";
|
||||
private const int FlushThreshold = 4096;
|
||||
private const int MaxBufferedEntries = 16;
|
||||
private static readonly ThreadLocal<StringBuilder?> s_threadBuilder = new(() => StringBuilderPool.Rent(), trackAllValues: true);
|
||||
private static readonly ThreadLocal<int> s_threadEntryCount = new(() => 0, trackAllValues: true);
|
||||
private static readonly object s_fileLock = new();
|
||||
private static readonly string s_logPath = Path.Combine(AppContext.BaseDirectory, LogFileName);
|
||||
private static readonly UTF8Encoding s_utf8NoBom = new(false);
|
||||
|
||||
static HeuristicFailureLog()
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit += static (_, _) => FlushAll(force: true);
|
||||
}
|
||||
|
||||
public static void Record(ulong prime, ulong? candidateOrder, HeuristicFailureReason reason)
|
||||
{
|
||||
string primeText = prime.ToString(CultureInfo.InvariantCulture);
|
||||
string? candidateText = candidateOrder.HasValue ? candidateOrder.Value.ToString(CultureInfo.InvariantCulture) : null;
|
||||
RecordInternal(primeText, candidateText, reason);
|
||||
}
|
||||
|
||||
public static void Record(UInt128 prime, UInt128? candidateOrder, HeuristicFailureReason reason)
|
||||
{
|
||||
string primeText = prime.ToString(CultureInfo.InvariantCulture);
|
||||
string? candidateText = candidateOrder.HasValue ? candidateOrder.Value.ToString(CultureInfo.InvariantCulture) : null;
|
||||
RecordInternal(primeText, candidateText, reason);
|
||||
}
|
||||
|
||||
private static void RecordInternal(string primeText, string? candidateText, HeuristicFailureReason reason)
|
||||
{
|
||||
StringBuilder? existing = s_threadBuilder.Value;
|
||||
StringBuilder builder = existing ?? StringBuilderPool.Rent();
|
||||
int count = s_threadEntryCount.Value + 1;
|
||||
bool forceFlush = count >= MaxBufferedEntries;
|
||||
builder.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
|
||||
builder.Append(" | prime=");
|
||||
builder.Append(primeText);
|
||||
builder.Append(" | reason=");
|
||||
builder.Append(reason);
|
||||
if (candidateText is not null)
|
||||
{
|
||||
builder.Append(" | candidateOrder=");
|
||||
builder.Append(candidateText);
|
||||
}
|
||||
|
||||
builder.AppendLine();
|
||||
FlushBuilderIfNeeded(builder, forceFlush);
|
||||
if (builder.Length == 0)
|
||||
{
|
||||
count = 0;
|
||||
}
|
||||
|
||||
s_threadEntryCount.Value = count;
|
||||
s_threadBuilder.Value = builder;
|
||||
}
|
||||
|
||||
private static void FlushAll(bool force)
|
||||
{
|
||||
foreach (StringBuilder? builder in s_threadBuilder.Values)
|
||||
{
|
||||
if (builder is not null)
|
||||
{
|
||||
FlushBuilderIfNeeded(builder, force);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void FlushBuilderIfNeeded(StringBuilder builder, bool force)
|
||||
{
|
||||
if (builder.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && builder.Length < FlushThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string text = builder.ToString();
|
||||
builder.Clear();
|
||||
lock (s_fileLock)
|
||||
{
|
||||
using FileStream stream = new FileStream(s_logPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||
using StreamWriter writer = new StreamWriter(stream, s_utf8NoBom);
|
||||
writer.Write(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace PerfectNumbers.Core
|
||||
{
|
||||
internal enum HeuristicFailureReason
|
||||
{
|
||||
PhiPartialFactorizationFailed,
|
||||
StrictModeRequested,
|
||||
HeuristicPipelineUnresolved,
|
||||
HeuristicPipelineAssumedFullOrder,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,6 @@ public interface IMersenneNumberDivisorByDivisorTester
|
||||
|
||||
public interface IDivisorScanSession : IDisposable
|
||||
{
|
||||
void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits);
|
||||
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,9 @@ 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;
|
||||
|
||||
@@ -24,31 +26,124 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
return;
|
||||
}
|
||||
|
||||
_ = threadCount;
|
||||
candidates.Sort();
|
||||
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
||||
int candidateCount = candidateSpan.Length;
|
||||
|
||||
bool applyStartPrime = startPrime > 0UL;
|
||||
int skippedByPreviousResults = 0;
|
||||
List<ulong> primesToTest = new(candidates.Count);
|
||||
|
||||
if (previousResults is not null && previousResults.Count > 0)
|
||||
{
|
||||
int recordedCount = previousResults.Count;
|
||||
bool preferSortedMerge = recordedCount <= 4096 || (recordedCount * 4L) <= candidateCount;
|
||||
if (preferSortedMerge)
|
||||
{
|
||||
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();
|
||||
|
||||
int writeIndex = 0;
|
||||
int skipIndex = 0;
|
||||
|
||||
for (int readIndex = 0; readIndex < candidateCount; readIndex++)
|
||||
{
|
||||
ulong candidate = candidateSpan[readIndex];
|
||||
|
||||
while (skipIndex < recordedCount && recordedSpan[skipIndex] < candidate)
|
||||
{
|
||||
skipIndex++;
|
||||
}
|
||||
|
||||
if (skipIndex < recordedCount && recordedSpan[skipIndex] == candidate)
|
||||
{
|
||||
skippedByPreviousResults++;
|
||||
skipIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateSpan[writeIndex++] = candidate;
|
||||
}
|
||||
|
||||
if (writeIndex < candidateCount)
|
||||
{
|
||||
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
|
||||
candidateSpan = CollectionsMarshal.AsSpan(candidates); // candidateSpan now tracks the reduced list.
|
||||
candidateCount = candidateSpan.Length;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(recordedCandidates, clearArray: true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int writeIndex = 0;
|
||||
for (int readIndex = 0; readIndex < candidateCount; readIndex++)
|
||||
{
|
||||
ulong candidate = candidateSpan[readIndex];
|
||||
|
||||
if (previousResults.ContainsKey(candidate))
|
||||
{
|
||||
skippedByPreviousResults++;
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateSpan[writeIndex++] = candidate;
|
||||
}
|
||||
|
||||
if (writeIndex < candidateCount)
|
||||
{
|
||||
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
|
||||
candidateSpan = CollectionsMarshal.AsSpan(candidates); // candidateSpan now tracks the reduced list.
|
||||
candidateCount = candidateSpan.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (int index = 0; index < candidates.Count; index++)
|
||||
for (int index = startIndex; index < candidateCount; index++)
|
||||
{
|
||||
ulong candidate = candidates[index];
|
||||
|
||||
if (applyStartPrime && candidate < startPrime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (previousResults is not null && previousResults.ContainsKey(candidate))
|
||||
{
|
||||
skippedByPreviousResults++;
|
||||
continue;
|
||||
}
|
||||
ulong candidate = primesSpan[index];
|
||||
|
||||
if (candidate <= 1UL)
|
||||
{
|
||||
@@ -75,17 +170,17 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
continue;
|
||||
}
|
||||
|
||||
primesSpan[primeWriteIndex++] = candidate;
|
||||
|
||||
if (candidate > maxPrime)
|
||||
{
|
||||
maxPrime = candidate;
|
||||
}
|
||||
|
||||
primesToTest.Add(candidate);
|
||||
}
|
||||
|
||||
if (skippedByPreviousResults > 0)
|
||||
if (primeWriteIndex < primesToTest.Count)
|
||||
{
|
||||
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
|
||||
primesToTest.RemoveRange(primeWriteIndex, primesToTest.Count - primeWriteIndex);
|
||||
}
|
||||
|
||||
if (primesToTest.Count == 0)
|
||||
@@ -158,7 +253,13 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ulong prime in filteredPrimes)
|
||||
int workerCount = threadCount <= 0 ? Environment.ProcessorCount : threadCount;
|
||||
if (workerCount < 1)
|
||||
{
|
||||
workerCount = 1;
|
||||
}
|
||||
|
||||
void ProcessPrime(ulong prime)
|
||||
{
|
||||
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
|
||||
|
||||
@@ -166,11 +267,53 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
markComposite();
|
||||
printResult(prime, true, true, false);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
clearComposite();
|
||||
printResult(prime, true, divisorsExhausted, true);
|
||||
}
|
||||
|
||||
if (workerCount == 1)
|
||||
{
|
||||
foreach (ulong prime in filteredPrimes)
|
||||
{
|
||||
ProcessPrime(prime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ParallelOptions options = new()
|
||||
{
|
||||
MaxDegreeOfParallelism = workerCount
|
||||
};
|
||||
|
||||
primeCount = filteredPrimes.Count; // Reusing primeCount to track the filtered count.
|
||||
int primesPerWorker = (primeCount + workerCount - 1) / workerCount;
|
||||
|
||||
Parallel.For(
|
||||
0,
|
||||
workerCount,
|
||||
options,
|
||||
workerIndex =>
|
||||
{
|
||||
int start = workerIndex * primesPerWorker;
|
||||
if (start >= primeCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int end = start + primesPerWorker;
|
||||
if (end > primeCount)
|
||||
{
|
||||
end = primeCount;
|
||||
}
|
||||
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
ProcessPrime(filteredPrimes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,10 +371,6 @@ public static class MersennePrimeFactorTester
|
||||
return 2UL;
|
||||
}
|
||||
|
||||
var rng = new Random(1234567); // TODO: Replace this RNG with the deterministic span-based
|
||||
// sequence measured faster in PollardRhoBenchmarks so we avoid
|
||||
// per-call Random allocations.
|
||||
|
||||
ulong c, d, x, y, diff;
|
||||
while (true)
|
||||
{
|
||||
@@ -383,8 +379,8 @@ public static class MersennePrimeFactorTester
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
c = (ulong)rng.Next(2, int.MaxValue);
|
||||
x = (ulong)rng.Next(2, int.MaxValue);
|
||||
c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
|
||||
x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
|
||||
y = x;
|
||||
d = 1UL;
|
||||
|
||||
@@ -523,9 +519,6 @@ public static class MersennePrimeFactorTester
|
||||
return 2;
|
||||
}
|
||||
|
||||
var rng = new Random(1234567); // TODO: Replace with the deterministic UInt128 sequence from
|
||||
// PollardRho128Benchmarks so we eliminate Random allocations.
|
||||
|
||||
UInt128 c, d, x, y, diff;
|
||||
while (true)
|
||||
{
|
||||
@@ -534,8 +527,8 @@ public static class MersennePrimeFactorTester
|
||||
return 0;
|
||||
}
|
||||
|
||||
c = (UInt128)rng.NextInt64(2, long.MaxValue);
|
||||
x = (UInt128)rng.NextInt64(2, long.MaxValue);
|
||||
c = (DeterministicRandom.NextUInt128() % (n - 1)) + 1;
|
||||
x = (DeterministicRandom.NextUInt128() % (n - 2)) + 2;
|
||||
y = x;
|
||||
d = 1;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -14,29 +14,6 @@ public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong
|
||||
|
||||
public readonly ulong MontgomeryTwo = montgomeryTwo;
|
||||
public readonly ulong MontgomeryTwoSquared = montgomeryTwoSquared;
|
||||
}
|
||||
|
||||
internal static class MontgomeryDivisorDataCache
|
||||
{
|
||||
private static readonly ConcurrentDictionary<ulong, MontgomeryDivisorData> Cache = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MontgomeryDivisorData Get(ulong modulus) => Cache.GetOrAdd(modulus, static m => Create(m));
|
||||
|
||||
private static MontgomeryDivisorData Create(ulong modulus)
|
||||
{
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64x2, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
nPrime,
|
||||
montgomeryOne,
|
||||
montgomeryTwo,
|
||||
montgomeryTwoSquared);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ComputeMontgomeryResidue(UInt128 value, ulong modulus) => (ulong)(value % modulus);
|
||||
@@ -53,4 +30,47 @@ internal static class MontgomeryDivisorDataCache
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
|
||||
public static MontgomeryDivisorData FromModulus(ulong modulus)
|
||||
{
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64x2, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
nPrime,
|
||||
montgomeryOne,
|
||||
montgomeryTwo,
|
||||
montgomeryTwoSquared);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static class MontgomeryDivisorDataCache
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static bool s_hasCachedValue;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong s_cachedModulus;
|
||||
|
||||
[ThreadStatic]
|
||||
private static MontgomeryDivisorData s_cachedData;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MontgomeryDivisorData Get(ulong modulus)
|
||||
{
|
||||
if (s_hasCachedValue && s_cachedModulus == modulus)
|
||||
{
|
||||
return s_cachedData;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData computed = MontgomeryDivisorData.FromModulus(modulus);
|
||||
s_cachedModulus = modulus;
|
||||
s_cachedData = computed;
|
||||
s_hasCachedValue = true;
|
||||
return computed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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();
|
||||
}
|
||||
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;
|
||||
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
|
||||
}
|
||||
|
||||
_next = s_poolHead;
|
||||
s_poolHead = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ public static class PerfectNumberConstants
|
||||
public const ulong ExtraDivisorCycleSearchLimit = 64UL;
|
||||
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
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="EvenPerfectBitScanner.Tests" />
|
||||
<InternalsVisibleTo Include="PerfectNumbers.Core.Tests" />
|
||||
<InternalsVisibleTo Include="ILGPURuntime" />
|
||||
<InternalsVisibleTo Include="HeuristicMultiplyShiftTiming" />
|
||||
<InternalsVisibleTo Include="EvenPerfectBitScanner.Benchmarks" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
internal readonly struct PollardRhoMontgomeryReducer
|
||||
{
|
||||
private readonly MontgomeryDivisorData _divisorData;
|
||||
private readonly ulong _reciprocal;
|
||||
|
||||
[ThreadStatic]
|
||||
private static bool s_hasCachedReciprocal;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong s_cachedReciprocal;
|
||||
|
||||
public PollardRhoMontgomeryReducer(in MontgomeryDivisorData divisorData, ulong reciprocal)
|
||||
{
|
||||
_divisorData = divisorData;
|
||||
_reciprocal = reciprocal == 0UL || reciprocal == ulong.MaxValue
|
||||
? ReciprocalMath.ComputeReciprocalEstimate(divisorData.Modulus)
|
||||
: reciprocal;
|
||||
}
|
||||
|
||||
public ulong Modulus => _divisorData.Modulus;
|
||||
private ulong NPrime => _divisorData.NPrime;
|
||||
private ulong MontgomeryTwoSquared => _divisorData.MontgomeryTwoSquared;
|
||||
private ulong MontgomeryOne => _divisorData.MontgomeryOne;
|
||||
private ulong MontgomeryTwo => _divisorData.MontgomeryTwo;
|
||||
public ulong Reciprocal => _reciprocal;
|
||||
|
||||
public static PollardRhoMontgomeryReducer Create(ulong modulus)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(modulus);
|
||||
ulong reciprocal;
|
||||
if (s_hasCachedReciprocal)
|
||||
{
|
||||
reciprocal = ReciprocalMath.RefineReciprocalEstimate(s_cachedReciprocal, modulus);
|
||||
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
|
||||
{
|
||||
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(modulus);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(modulus);
|
||||
}
|
||||
|
||||
s_cachedReciprocal = reciprocal;
|
||||
s_hasCachedReciprocal = true;
|
||||
return new PollardRhoMontgomeryReducer(divisorData, reciprocal);
|
||||
}
|
||||
|
||||
public static PollardRhoMontgomeryReducer Create(in MontgomeryDivisorData divisorData, ulong previousReciprocal)
|
||||
{
|
||||
ulong reciprocal = ReciprocalMath.RefineReciprocalEstimate(previousReciprocal, divisorData.Modulus);
|
||||
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
|
||||
{
|
||||
reciprocal = ReciprocalMath.ComputeReciprocalEstimate(divisorData.Modulus);
|
||||
}
|
||||
|
||||
s_cachedReciprocal = reciprocal;
|
||||
s_hasCachedReciprocal = true;
|
||||
return new PollardRhoMontgomeryReducer(divisorData, reciprocal);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong ConvertToMontgomery(ulong value)
|
||||
{
|
||||
if (value == 0UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
if (value == 1UL)
|
||||
{
|
||||
return MontgomeryOne;
|
||||
}
|
||||
|
||||
if (value == 2UL)
|
||||
{
|
||||
return MontgomeryTwo;
|
||||
}
|
||||
|
||||
return value.MontgomeryMultiply(MontgomeryTwoSquared, Modulus, NPrime);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong ReduceFromMontgomery(ulong montgomeryValue)
|
||||
{
|
||||
return montgomeryValue.MontgomeryMultiply(1UL, Modulus, NPrime);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong Advance(ulong montgomeryValue, ulong cMontgomery)
|
||||
{
|
||||
ulong squared = montgomeryValue.MontgomeryMultiply(montgomeryValue, Modulus, NPrime);
|
||||
return MontgomeryAdd(squared, cMontgomery, Modulus);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong AdvanceStandard(ulong standardValue, ulong c)
|
||||
{
|
||||
UInt128 polynomial = (UInt128)standardValue * standardValue + c;
|
||||
return ReciprocalMath.Reduce(polynomial, Modulus, _reciprocal);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong MontgomeryAdd(ulong a, ulong b, ulong modulus)
|
||||
{
|
||||
UInt128 total = (UInt128)a + b;
|
||||
if (total >= modulus)
|
||||
{
|
||||
total -= modulus;
|
||||
}
|
||||
|
||||
return (ulong)total;
|
||||
}
|
||||
}
|
||||
@@ -9,29 +9,18 @@ namespace PerfectNumbers.Core;
|
||||
|
||||
internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
internal readonly struct PrimeOrderResultWide
|
||||
{
|
||||
public PrimeOrderResultWide(PrimeOrderStatus status, UInt128 order)
|
||||
{
|
||||
Status = status;
|
||||
Order = order;
|
||||
}
|
||||
|
||||
public PrimeOrderStatus Status { get; }
|
||||
|
||||
public UInt128 Order { get; }
|
||||
}
|
||||
|
||||
public static PrimeOrderResultWide Calculate(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
|
||||
public static UInt128 Calculate(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
|
||||
=> Calculate(prime, previousOrder, config, PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
public static PrimeOrderResultWide Calculate(
|
||||
public static UInt128 Calculate(
|
||||
UInt128 prime,
|
||||
UInt128? previousOrder,
|
||||
PrimeOrderSearchConfig config,
|
||||
PrimeOrderHeuristicDevice device)
|
||||
{
|
||||
using var scope = UsePow2Mode(device);
|
||||
var scope = UsePow2Mode(device);
|
||||
MontgomeryDivisorData divisorData;
|
||||
UInt128 result;
|
||||
if (prime <= ulong.MaxValue)
|
||||
{
|
||||
ulong? previous = null;
|
||||
@@ -48,77 +37,90 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
}
|
||||
|
||||
PrimeOrderResult result = Calculate((ulong)prime, previous, config, device);
|
||||
return new PrimeOrderResultWide(result.Status, result.Order);
|
||||
ulong prime64 = (ulong)prime;
|
||||
divisorData = MontgomeryDivisorData.FromModulus(prime64);
|
||||
ulong order64 = Calculate(prime64, previous, divisorData, config, device);
|
||||
result = order64 == 0UL ? UInt128.Zero : (UInt128)order64;
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorData = default;
|
||||
result = CalculateWideInternal(prime, previousOrder, divisorData, config);
|
||||
}
|
||||
|
||||
return CalculateWideInternal(prime, previousOrder, config);
|
||||
scope.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static PrimeOrderResultWide CalculateWideInternal(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
|
||||
private static UInt128 CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (prime <= UInt128.One)
|
||||
{
|
||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, UInt128.One);
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (prime == (UInt128)3UL)
|
||||
{
|
||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, (UInt128)2UL);
|
||||
return (UInt128)2UL;
|
||||
}
|
||||
|
||||
UInt128 phi = prime - UInt128.One;
|
||||
using var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
||||
var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
||||
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
||||
UInt128 result;
|
||||
if (phiFactors.Factors is null)
|
||||
{
|
||||
return FinishStrictlyWide(prime, config.Mode);
|
||||
result = FinishStrictlyWide(prime, divisorData);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
|
||||
}
|
||||
|
||||
return RunHeuristicPipelineWide(prime, previousOrder, config, phi, phiFactors);
|
||||
debugScope.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static PrimeOrderResultWide RunHeuristicPipelineWide(
|
||||
private static UInt128 RunHeuristicPipelineWide(
|
||||
UInt128 prime,
|
||||
UInt128? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
PrimeOrderSearchConfig config,
|
||||
UInt128 phi,
|
||||
PartialFactorResult128 phiFactors)
|
||||
{
|
||||
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, phiFactors))
|
||||
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
|
||||
{
|
||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, phi);
|
||||
return phi;
|
||||
}
|
||||
|
||||
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi);
|
||||
candidateOrder = ExponentLoweringWide(candidateOrder, prime, phiFactors);
|
||||
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
|
||||
candidateOrder = ExponentLoweringWide(candidateOrder, prime, divisorData, phiFactors);
|
||||
|
||||
if (TryConfirmOrderWide(prime, candidateOrder, config))
|
||||
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
|
||||
{
|
||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, candidateOrder);
|
||||
return candidateOrder;
|
||||
}
|
||||
|
||||
if (config.Mode == PrimeOrderMode.Strict)
|
||||
{
|
||||
return FinishStrictlyWide(prime, PrimeOrderMode.Strict);
|
||||
return FinishStrictlyWide(prime, divisorData);
|
||||
}
|
||||
|
||||
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, config, out UInt128 order))
|
||||
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
|
||||
{
|
||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, order);
|
||||
return order;
|
||||
}
|
||||
|
||||
return FinishStrictlyWide(prime, config.Mode);
|
||||
return candidateOrder;
|
||||
}
|
||||
|
||||
private static PrimeOrderResultWide FinishStrictlyWide(UInt128 prime, PrimeOrderMode mode)
|
||||
private static UInt128 FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
UInt128 strictOrder = CalculateByFactorizationWide(prime);
|
||||
PrimeOrderStatus status = mode == PrimeOrderMode.Strict ? PrimeOrderStatus.Found : PrimeOrderStatus.HeuristicUnresolved;
|
||||
return new PrimeOrderResultWide(status, strictOrder);
|
||||
return CalculateByFactorizationWide(prime, divisorData);
|
||||
}
|
||||
|
||||
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, PartialFactorResult128 factors)
|
||||
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
||||
{
|
||||
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
||||
int length = factors.Count;
|
||||
@@ -126,7 +128,7 @@ internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
UInt128 factor = factorSpan[i].Value;
|
||||
UInt128 reduced = phi / factor;
|
||||
if (Pow2ModWide(reduced, prime) == UInt128.One)
|
||||
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -135,14 +137,14 @@ internal static partial class PrimeOrderCalculator
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UInt128 InitializeStartingOrderWide(UInt128 prime, UInt128 phi)
|
||||
private static UInt128 InitializeStartingOrderWide(UInt128 prime, UInt128 phi, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
UInt128 order = phi;
|
||||
UInt128 mod8 = prime & (UInt128)7UL;
|
||||
if (mod8 == UInt128.One || mod8 == (UInt128)7UL)
|
||||
{
|
||||
UInt128 half = phi >> 1;
|
||||
if (Pow2ModWide(half, prime) == UInt128.One)
|
||||
if (Pow2ModWide(half, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = half;
|
||||
}
|
||||
@@ -151,7 +153,7 @@ internal static partial class PrimeOrderCalculator
|
||||
return order;
|
||||
}
|
||||
|
||||
private static UInt128 ExponentLoweringWide(UInt128 order, UInt128 prime, PartialFactorResult128 factors)
|
||||
private static UInt128 ExponentLoweringWide(UInt128 order, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
||||
{
|
||||
FactorEntry128[]? tempArray = null;
|
||||
try
|
||||
@@ -184,7 +186,7 @@ internal static partial class PrimeOrderCalculator
|
||||
if ((order % primeFactor) == UInt128.Zero)
|
||||
{
|
||||
UInt128 reduced = order / primeFactor;
|
||||
if (Pow2ModWide(reduced, prime) == UInt128.One)
|
||||
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = reduced;
|
||||
continue;
|
||||
@@ -206,14 +208,14 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryConfirmOrderWide(UInt128 prime, UInt128 order, PrimeOrderSearchConfig config)
|
||||
private static bool TryConfirmOrderWide(UInt128 prime, UInt128 order, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (order == UInt128.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Pow2ModWide(order, prime) != UInt128.One)
|
||||
if (Pow2ModWide(order, prime, divisorData) != UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -253,7 +255,7 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
|
||||
reduced /= primeFactor;
|
||||
if (Pow2ModWide(reduced, prime) == UInt128.One)
|
||||
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -267,6 +269,7 @@ internal static partial class PrimeOrderCalculator
|
||||
UInt128 prime,
|
||||
UInt128 order,
|
||||
UInt128? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
PrimeOrderSearchConfig config,
|
||||
out UInt128 result)
|
||||
{
|
||||
@@ -322,12 +325,12 @@ internal static partial class PrimeOrderCalculator
|
||||
UInt128 candidate = candidates[i];
|
||||
powUsed++;
|
||||
|
||||
if (Pow2ModWide(candidate, prime) != UInt128.One)
|
||||
if (Pow2ModWide(candidate, prime, divisorData) != UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryConfirmCandidateWide(prime, candidate, config, ref powUsed, powBudget))
|
||||
if (!TryConfirmCandidateWide(prime, candidate, config, ref powUsed, powBudget, divisorData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -511,7 +514,7 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryConfirmCandidateWide(UInt128 prime, UInt128 candidate, PrimeOrderSearchConfig config, ref int powUsed, int powBudget)
|
||||
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)
|
||||
@@ -554,7 +557,7 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
|
||||
powUsed++;
|
||||
if (Pow2ModWide(reduced, prime) == UInt128.One)
|
||||
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -826,7 +829,7 @@ internal static partial class PrimeOrderCalculator
|
||||
return 64 + BitOperations.TrailingZeroCount(high);
|
||||
}
|
||||
|
||||
private static UInt128 CalculateByFactorizationWide(UInt128 prime)
|
||||
private static UInt128 CalculateByFactorizationWide(in UInt128 prime, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
UInt128 phi = prime - UInt128.One;
|
||||
Dictionary<UInt128, int> counts = new(capacity: 8);
|
||||
@@ -850,7 +853,7 @@ internal static partial class PrimeOrderCalculator
|
||||
if ((order % primeFactor) != UInt128.Zero)
|
||||
{
|
||||
UInt128 candidate = order / primeFactor;
|
||||
if (Pow2ModWide(candidate, prime) == UInt128.One)
|
||||
if (Pow2ModWide(candidate, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = candidate;
|
||||
}
|
||||
@@ -996,7 +999,7 @@ internal static partial class PrimeOrderCalculator
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UInt128 Pow2ModWide(UInt128 exponent, UInt128 modulus)
|
||||
private static UInt128 Pow2ModWide(UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
if (modulus == UInt128.One)
|
||||
{
|
||||
@@ -1009,7 +1012,7 @@ internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
ulong prime64 = (ulong)modulus;
|
||||
ulong exponent64 = (ulong)exponent;
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent64, prime64, out ulong remainder);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent64, prime64, out ulong remainder, divisorData);
|
||||
if (status == GpuPow2ModStatus.Success)
|
||||
{
|
||||
return remainder;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,125 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
internal static class ReciprocalMath
|
||||
{
|
||||
private const int ReciprocalShift = 64;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong ComputeReciprocalEstimate(ulong divisor)
|
||||
{
|
||||
if (divisor <= 1UL)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
UInt128 scaledOne = UInt128.One << ReciprocalShift;
|
||||
UInt128 estimate = scaledOne / divisor;
|
||||
if ((scaledOne % divisor) != UInt128.Zero)
|
||||
{
|
||||
estimate += UInt128.One;
|
||||
}
|
||||
|
||||
if (estimate == UInt128.Zero)
|
||||
{
|
||||
return 1UL;
|
||||
}
|
||||
|
||||
if (estimate > ulong.MaxValue)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return (ulong)estimate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong RefineReciprocalEstimate(ulong reciprocal, ulong divisor)
|
||||
{
|
||||
if (divisor <= 1UL)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
|
||||
{
|
||||
return ComputeReciprocalEstimate(divisor);
|
||||
}
|
||||
|
||||
UInt128 reciprocal128 = reciprocal;
|
||||
UInt128 product = (UInt128)divisor * reciprocal128;
|
||||
UInt128 scaled = product >> ReciprocalShift;
|
||||
UInt128 twoScaled = UInt128.One << (ReciprocalShift + 1);
|
||||
if (scaled >= twoScaled)
|
||||
{
|
||||
return ComputeReciprocalEstimate(divisor);
|
||||
}
|
||||
|
||||
UInt128 correction = twoScaled - scaled;
|
||||
UInt128 refined = (reciprocal128 * correction) >> ReciprocalShift;
|
||||
if (refined == UInt128.Zero)
|
||||
{
|
||||
return 1UL;
|
||||
}
|
||||
|
||||
return refined > ulong.MaxValue ? ulong.MaxValue : (ulong)refined;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Reduce(UInt128 numerator, ulong divisor, ulong reciprocal)
|
||||
{
|
||||
if (divisor <= 1UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
if (numerator == UInt128.Zero)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
if (reciprocal == 0UL || reciprocal == ulong.MaxValue)
|
||||
{
|
||||
return (ulong)(numerator % divisor);
|
||||
}
|
||||
|
||||
ulong high = (ulong)(numerator >> ReciprocalShift);
|
||||
ulong low = (ulong)numerator;
|
||||
|
||||
if (high >= divisor)
|
||||
{
|
||||
return (ulong)(numerator % divisor);
|
||||
}
|
||||
|
||||
UInt128 highProduct = (UInt128)high * reciprocal;
|
||||
UInt128 lowProduct = (UInt128)low * reciprocal;
|
||||
UInt128 estimate = highProduct + (lowProduct >> ReciprocalShift);
|
||||
|
||||
if (estimate == UInt128.Zero)
|
||||
{
|
||||
return (ulong)(numerator % divisor);
|
||||
}
|
||||
|
||||
UInt128 product = estimate * divisor;
|
||||
while (product > numerator)
|
||||
{
|
||||
if (estimate == UInt128.Zero)
|
||||
{
|
||||
return (ulong)(numerator % divisor);
|
||||
}
|
||||
|
||||
estimate--;
|
||||
product -= divisor;
|
||||
}
|
||||
|
||||
UInt128 remainder = numerator - product;
|
||||
while (remainder >= divisor)
|
||||
{
|
||||
remainder -= divisor;
|
||||
}
|
||||
|
||||
return (ulong)remainder;
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,7 @@ public static class UInt128Extensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
||||
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||
{
|
||||
if (bitIndex >= 64)
|
||||
{
|
||||
@@ -229,18 +229,16 @@ public static class UInt128Extensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
{
|
||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
||||
if (windowStart != 0)
|
||||
{
|
||||
GpuUInt128 shifted = exponent;
|
||||
shifted.ShiftRight(windowStart);
|
||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
||||
return shifted.Low & mask;
|
||||
exponent.ShiftRight(windowStart);
|
||||
return exponent.Low & mask;
|
||||
}
|
||||
|
||||
ulong directMask = (1UL << windowBitCount) - 1UL;
|
||||
return exponent.Low & directMask;
|
||||
return exponent.Low & mask;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace PerfectNumbers.Core
|
||||
{
|
||||
public static class UInt128Numbers
|
||||
@@ -9,6 +11,8 @@ namespace PerfectNumbers.Core
|
||||
public static readonly UInt128 OneShiftedLeft64 = UInt128.One << 64;
|
||||
public static readonly UInt128 OneShiftedLeft64x2 = Two * OneShiftedLeft64;
|
||||
|
||||
public static readonly GpuUInt128 TwoGpu = new(2UL);
|
||||
|
||||
// TODO: Populate this table from the UInt128 residue benchmarks so GPU kernels can pull precomputed constants
|
||||
// without re-deriving them at runtime.
|
||||
// TODO: Extend the table with cached Mod3/Mod5 folding constants so both CPU and GPU residue helpers can reuse
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU.Algorithms;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
@@ -11,412 +13,6 @@ public static class ULongExtensions
|
||||
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
||||
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSize - 1);
|
||||
|
||||
private struct Pow2OddPowerTable
|
||||
{
|
||||
public ulong Element0;
|
||||
public ulong Element1;
|
||||
public ulong Element2;
|
||||
public ulong Element3;
|
||||
public ulong Element4;
|
||||
public ulong Element5;
|
||||
public ulong Element6;
|
||||
public ulong Element7;
|
||||
public ulong Element8;
|
||||
public ulong Element9;
|
||||
public ulong Element10;
|
||||
public ulong Element11;
|
||||
public ulong Element12;
|
||||
public ulong Element13;
|
||||
public ulong Element14;
|
||||
public ulong Element15;
|
||||
public ulong Element16;
|
||||
public ulong Element17;
|
||||
public ulong Element18;
|
||||
public ulong Element19;
|
||||
public ulong Element20;
|
||||
public ulong Element21;
|
||||
public ulong Element22;
|
||||
public ulong Element23;
|
||||
public ulong Element24;
|
||||
public ulong Element25;
|
||||
public ulong Element26;
|
||||
public ulong Element27;
|
||||
public ulong Element28;
|
||||
public ulong Element29;
|
||||
public ulong Element30;
|
||||
public ulong Element31;
|
||||
public ulong Element32;
|
||||
public ulong Element33;
|
||||
public ulong Element34;
|
||||
public ulong Element35;
|
||||
public ulong Element36;
|
||||
public ulong Element37;
|
||||
public ulong Element38;
|
||||
public ulong Element39;
|
||||
public ulong Element40;
|
||||
public ulong Element41;
|
||||
public ulong Element42;
|
||||
public ulong Element43;
|
||||
public ulong Element44;
|
||||
public ulong Element45;
|
||||
public ulong Element46;
|
||||
public ulong Element47;
|
||||
public ulong Element48;
|
||||
public ulong Element49;
|
||||
public ulong Element50;
|
||||
public ulong Element51;
|
||||
public ulong Element52;
|
||||
public ulong Element53;
|
||||
public ulong Element54;
|
||||
public ulong Element55;
|
||||
public ulong Element56;
|
||||
public ulong Element57;
|
||||
public ulong Element58;
|
||||
public ulong Element59;
|
||||
public ulong Element60;
|
||||
public ulong Element61;
|
||||
public ulong Element62;
|
||||
public ulong Element63;
|
||||
public ulong Element64;
|
||||
public ulong Element65;
|
||||
public ulong Element66;
|
||||
public ulong Element67;
|
||||
public ulong Element68;
|
||||
public ulong Element69;
|
||||
public ulong Element70;
|
||||
public ulong Element71;
|
||||
public ulong Element72;
|
||||
public ulong Element73;
|
||||
public ulong Element74;
|
||||
public ulong Element75;
|
||||
public ulong Element76;
|
||||
public ulong Element77;
|
||||
public ulong Element78;
|
||||
public ulong Element79;
|
||||
public ulong Element80;
|
||||
public ulong Element81;
|
||||
public ulong Element82;
|
||||
public ulong Element83;
|
||||
public ulong Element84;
|
||||
public ulong Element85;
|
||||
public ulong Element86;
|
||||
public ulong Element87;
|
||||
public ulong Element88;
|
||||
public ulong Element89;
|
||||
public ulong Element90;
|
||||
public ulong Element91;
|
||||
public ulong Element92;
|
||||
public ulong Element93;
|
||||
public ulong Element94;
|
||||
public ulong Element95;
|
||||
public ulong Element96;
|
||||
public ulong Element97;
|
||||
public ulong Element98;
|
||||
public ulong Element99;
|
||||
public ulong Element100;
|
||||
public ulong Element101;
|
||||
public ulong Element102;
|
||||
public ulong Element103;
|
||||
public ulong Element104;
|
||||
public ulong Element105;
|
||||
public ulong Element106;
|
||||
public ulong Element107;
|
||||
public ulong Element108;
|
||||
public ulong Element109;
|
||||
public ulong Element110;
|
||||
public ulong Element111;
|
||||
public ulong Element112;
|
||||
public ulong Element113;
|
||||
public ulong Element114;
|
||||
public ulong Element115;
|
||||
public ulong Element116;
|
||||
public ulong Element117;
|
||||
public ulong Element118;
|
||||
public ulong Element119;
|
||||
public ulong Element120;
|
||||
public ulong Element121;
|
||||
public ulong Element122;
|
||||
public ulong Element123;
|
||||
public ulong Element124;
|
||||
public ulong Element125;
|
||||
public ulong Element126;
|
||||
public ulong Element127;
|
||||
|
||||
public ref ulong this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return ref GetElementRef(index);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ref ulong GetElementRef(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return ref Element0;
|
||||
case 1:
|
||||
return ref Element1;
|
||||
case 2:
|
||||
return ref Element2;
|
||||
case 3:
|
||||
return ref Element3;
|
||||
case 4:
|
||||
return ref Element4;
|
||||
case 5:
|
||||
return ref Element5;
|
||||
case 6:
|
||||
return ref Element6;
|
||||
case 7:
|
||||
return ref Element7;
|
||||
case 8:
|
||||
return ref Element8;
|
||||
case 9:
|
||||
return ref Element9;
|
||||
case 10:
|
||||
return ref Element10;
|
||||
case 11:
|
||||
return ref Element11;
|
||||
case 12:
|
||||
return ref Element12;
|
||||
case 13:
|
||||
return ref Element13;
|
||||
case 14:
|
||||
return ref Element14;
|
||||
case 15:
|
||||
return ref Element15;
|
||||
case 16:
|
||||
return ref Element16;
|
||||
case 17:
|
||||
return ref Element17;
|
||||
case 18:
|
||||
return ref Element18;
|
||||
case 19:
|
||||
return ref Element19;
|
||||
case 20:
|
||||
return ref Element20;
|
||||
case 21:
|
||||
return ref Element21;
|
||||
case 22:
|
||||
return ref Element22;
|
||||
case 23:
|
||||
return ref Element23;
|
||||
case 24:
|
||||
return ref Element24;
|
||||
case 25:
|
||||
return ref Element25;
|
||||
case 26:
|
||||
return ref Element26;
|
||||
case 27:
|
||||
return ref Element27;
|
||||
case 28:
|
||||
return ref Element28;
|
||||
case 29:
|
||||
return ref Element29;
|
||||
case 30:
|
||||
return ref Element30;
|
||||
case 31:
|
||||
return ref Element31;
|
||||
case 32:
|
||||
return ref Element32;
|
||||
case 33:
|
||||
return ref Element33;
|
||||
case 34:
|
||||
return ref Element34;
|
||||
case 35:
|
||||
return ref Element35;
|
||||
case 36:
|
||||
return ref Element36;
|
||||
case 37:
|
||||
return ref Element37;
|
||||
case 38:
|
||||
return ref Element38;
|
||||
case 39:
|
||||
return ref Element39;
|
||||
case 40:
|
||||
return ref Element40;
|
||||
case 41:
|
||||
return ref Element41;
|
||||
case 42:
|
||||
return ref Element42;
|
||||
case 43:
|
||||
return ref Element43;
|
||||
case 44:
|
||||
return ref Element44;
|
||||
case 45:
|
||||
return ref Element45;
|
||||
case 46:
|
||||
return ref Element46;
|
||||
case 47:
|
||||
return ref Element47;
|
||||
case 48:
|
||||
return ref Element48;
|
||||
case 49:
|
||||
return ref Element49;
|
||||
case 50:
|
||||
return ref Element50;
|
||||
case 51:
|
||||
return ref Element51;
|
||||
case 52:
|
||||
return ref Element52;
|
||||
case 53:
|
||||
return ref Element53;
|
||||
case 54:
|
||||
return ref Element54;
|
||||
case 55:
|
||||
return ref Element55;
|
||||
case 56:
|
||||
return ref Element56;
|
||||
case 57:
|
||||
return ref Element57;
|
||||
case 58:
|
||||
return ref Element58;
|
||||
case 59:
|
||||
return ref Element59;
|
||||
case 60:
|
||||
return ref Element60;
|
||||
case 61:
|
||||
return ref Element61;
|
||||
case 62:
|
||||
return ref Element62;
|
||||
case 63:
|
||||
return ref Element63;
|
||||
case 64:
|
||||
return ref Element64;
|
||||
case 65:
|
||||
return ref Element65;
|
||||
case 66:
|
||||
return ref Element66;
|
||||
case 67:
|
||||
return ref Element67;
|
||||
case 68:
|
||||
return ref Element68;
|
||||
case 69:
|
||||
return ref Element69;
|
||||
case 70:
|
||||
return ref Element70;
|
||||
case 71:
|
||||
return ref Element71;
|
||||
case 72:
|
||||
return ref Element72;
|
||||
case 73:
|
||||
return ref Element73;
|
||||
case 74:
|
||||
return ref Element74;
|
||||
case 75:
|
||||
return ref Element75;
|
||||
case 76:
|
||||
return ref Element76;
|
||||
case 77:
|
||||
return ref Element77;
|
||||
case 78:
|
||||
return ref Element78;
|
||||
case 79:
|
||||
return ref Element79;
|
||||
case 80:
|
||||
return ref Element80;
|
||||
case 81:
|
||||
return ref Element81;
|
||||
case 82:
|
||||
return ref Element82;
|
||||
case 83:
|
||||
return ref Element83;
|
||||
case 84:
|
||||
return ref Element84;
|
||||
case 85:
|
||||
return ref Element85;
|
||||
case 86:
|
||||
return ref Element86;
|
||||
case 87:
|
||||
return ref Element87;
|
||||
case 88:
|
||||
return ref Element88;
|
||||
case 89:
|
||||
return ref Element89;
|
||||
case 90:
|
||||
return ref Element90;
|
||||
case 91:
|
||||
return ref Element91;
|
||||
case 92:
|
||||
return ref Element92;
|
||||
case 93:
|
||||
return ref Element93;
|
||||
case 94:
|
||||
return ref Element94;
|
||||
case 95:
|
||||
return ref Element95;
|
||||
case 96:
|
||||
return ref Element96;
|
||||
case 97:
|
||||
return ref Element97;
|
||||
case 98:
|
||||
return ref Element98;
|
||||
case 99:
|
||||
return ref Element99;
|
||||
case 100:
|
||||
return ref Element100;
|
||||
case 101:
|
||||
return ref Element101;
|
||||
case 102:
|
||||
return ref Element102;
|
||||
case 103:
|
||||
return ref Element103;
|
||||
case 104:
|
||||
return ref Element104;
|
||||
case 105:
|
||||
return ref Element105;
|
||||
case 106:
|
||||
return ref Element106;
|
||||
case 107:
|
||||
return ref Element107;
|
||||
case 108:
|
||||
return ref Element108;
|
||||
case 109:
|
||||
return ref Element109;
|
||||
case 110:
|
||||
return ref Element110;
|
||||
case 111:
|
||||
return ref Element111;
|
||||
case 112:
|
||||
return ref Element112;
|
||||
case 113:
|
||||
return ref Element113;
|
||||
case 114:
|
||||
return ref Element114;
|
||||
case 115:
|
||||
return ref Element115;
|
||||
case 116:
|
||||
return ref Element116;
|
||||
case 117:
|
||||
return ref Element117;
|
||||
case 118:
|
||||
return ref Element118;
|
||||
case 119:
|
||||
return ref Element119;
|
||||
case 120:
|
||||
return ref Element120;
|
||||
case 121:
|
||||
return ref Element121;
|
||||
case 122:
|
||||
return ref Element122;
|
||||
case 123:
|
||||
return ref Element123;
|
||||
case 124:
|
||||
return ref Element124;
|
||||
case 125:
|
||||
return ref Element125;
|
||||
case 126:
|
||||
return ref Element126;
|
||||
case 127:
|
||||
return ref Element127;
|
||||
default:
|
||||
return ref Element0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ulong CalculateOrder(this ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
@@ -481,7 +77,7 @@ public static class ULongExtensions
|
||||
public static ulong MultiplyShiftRightShiftFirst(ulong value, ulong multiplier, int shift)
|
||||
{
|
||||
ulong high = value >> shift;
|
||||
ulong mask = (1UL << shift) - 1UL;
|
||||
ulong mask = (1UL << shift) - 1UL;
|
||||
ulong low = value & mask;
|
||||
|
||||
UInt128 highContribution = (UInt128)high * multiplier;
|
||||
@@ -731,7 +327,7 @@ public static class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWindowed(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||
public static ulong Pow2MontgomeryModWindowedCpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (exponent == 0UL)
|
||||
@@ -744,14 +340,84 @@ public static class ULongExtensions
|
||||
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
|
||||
}
|
||||
|
||||
int bitLength = BitOperations.Log2(exponent) + 1;
|
||||
int bitLength = GetPortableBitLength(exponent);
|
||||
int windowSize = GetWindowSize(bitLength);
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
Pow2OddPowerTable oddPowers = default;
|
||||
InitializeMontgomeryOddPowers(divisor, modulus, nPrime, ref oddPowers, oddPowerCount);
|
||||
ulong[] oddPowers = InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowerCount);
|
||||
|
||||
int index = bitLength - 1;
|
||||
while (index >= 0)
|
||||
{
|
||||
if (((exponent >> index) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
int windowStart = index - windowSize + 1;
|
||||
if (windowStart < 0)
|
||||
{
|
||||
windowStart = 0;
|
||||
}
|
||||
|
||||
while (((exponent >> windowStart) & 1UL) == 0UL)
|
||||
{
|
||||
windowStart++;
|
||||
}
|
||||
|
||||
int windowLength = index - windowStart + 1;
|
||||
for (int square = 0; square < windowLength; square++)
|
||||
{
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
}
|
||||
|
||||
ulong mask = (1UL << windowLength) - 1UL;
|
||||
ulong windowValue = (exponent >> windowStart) & mask;
|
||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||
ulong multiplier = oddPowers[tableIndex];
|
||||
result = result.MontgomeryMultiply(multiplier, modulus, nPrime);
|
||||
|
||||
index = windowStart - 1;
|
||||
}
|
||||
|
||||
if (oddPowerCount >= PerfectNumberConstants.PooledArrayThreshold)
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(oddPowers);
|
||||
}
|
||||
|
||||
if (keepMontgomery)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWindowedGpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
|
||||
}
|
||||
|
||||
if (exponent <= Pow2WindowFallbackThreshold)
|
||||
{
|
||||
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
|
||||
}
|
||||
|
||||
int bitLength = GetPortableBitLength(exponent);
|
||||
int windowSize = GetWindowSize(bitLength);
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
ulong[] oddPowers = InitializeMontgomeryOddPowersGpu(divisor, modulus, nPrime, oddPowerCount);
|
||||
|
||||
int index = bitLength - 1;
|
||||
while (index >= 0)
|
||||
@@ -826,6 +492,16 @@ public static class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPortableBitLength(ulong value)
|
||||
{
|
||||
if (value == 0UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 64 - XMath.LeadingZeroCount(value);
|
||||
}
|
||||
|
||||
private static int GetWindowSize(int bitLength)
|
||||
{
|
||||
if (bitLength <= 6)
|
||||
@@ -857,12 +533,36 @@ public static class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InitializeMontgomeryOddPowers(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, ref Pow2OddPowerTable oddPowers, int oddPowerCount)
|
||||
private static ulong[] InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
||||
{
|
||||
ulong[] oddPowers = oddPowerCount < PerfectNumberConstants.PooledArrayThreshold ? new ulong[oddPowerCount] : ArrayPool<ulong>.Shared.Rent(oddPowerCount);
|
||||
|
||||
oddPowers[0] = divisor.MontgomeryTwo;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return;
|
||||
return oddPowers;
|
||||
}
|
||||
|
||||
ulong previous,
|
||||
square = divisor.MontgomeryTwoSquared;
|
||||
|
||||
for (int i = 1; i < oddPowerCount; i++)
|
||||
{
|
||||
previous = oddPowers[i - 1];
|
||||
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime);
|
||||
}
|
||||
|
||||
return oddPowers;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong[] InitializeMontgomeryOddPowersGpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
||||
{
|
||||
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
||||
oddPowers[0] = divisor.MontgomeryTwo;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return oddPowers;
|
||||
}
|
||||
|
||||
ulong square = divisor.MontgomeryTwoSquared;
|
||||
@@ -871,20 +571,36 @@ public static class ULongExtensions
|
||||
ulong previous = oddPowers[i - 1];
|
||||
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime);
|
||||
}
|
||||
|
||||
return oddPowers;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModWithCycle(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||
public static ulong Pow2MontgomeryModWithCycleCpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
ulong rotationCount = exponent % cycleLength;
|
||||
return Pow2MontgomeryModWindowed(rotationCount, divisor, keepMontgomery: false);
|
||||
return Pow2MontgomeryModWindowedCpu(rotationCount, divisor, keepMontgomery: false);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModFromCycleRemainderCpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
return Pow2MontgomeryModWindowedCpu(reducedExponent, divisor, keepMontgomery: false);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModFromCycleRemainder(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||
public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
return Pow2MontgomeryModWindowed(reducedExponent, divisor, keepMontgomery: false);
|
||||
ulong rotationCount = exponent % cycleLength;
|
||||
return Pow2MontgomeryModWindowedGpu(rotationCount, divisor, keepMontgomery: false);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Pow2MontgomeryModFromCycleRemainderGpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
return Pow2MontgomeryModWindowedGpu(reducedExponent, divisor, keepMontgomery: false);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
# CPU `--mersenne=bydivisor` pipeline notes
|
||||
|
||||
This document summarizes how the `EvenPerfectBitScanner` executable executes the
|
||||
`--mersenne=bydivisor` mode on the CPU, and how the analytic observations we
|
||||
derived for the quotients
|
||||
|
||||
\[
|
||||
Q_k = \frac{N}{d_k}, \qquad d_k = 2pk + 1, \qquad N = 2^p - 1,
|
||||
\]
|
||||
|
||||
can help us identify concrete optimization opportunities.
|
||||
|
||||
## High-level control flow
|
||||
|
||||
* `Program.Main` wires `_byDivisorTester` to a CPU implementation whenever the
|
||||
CLI switches resolve to `--mersenne=bydivisor` and
|
||||
`--mersenne-device=cpu`. The CLI also forces a block size of 1 so that the
|
||||
pipeline pushes exponents to the tester one at a time.
|
||||
(`Program.cs`, lines 459-520.)
|
||||
* When the mode starts, `RunByDivisorMode` delegates to
|
||||
`MersenneNumberDivisorByDivisorTester.Run`, passing the candidate list from
|
||||
`--filter-p`, the CPU tester instance and the optional replay of previous
|
||||
outcomes. (`Program.cs`, lines 809-826.)
|
||||
* `MersenneNumberDivisorByDivisorTester.Run` sorts and deduplicates the
|
||||
candidate exponents, drops the ones already confirmed in prior runs, validates
|
||||
primality with the `Open.Numeric.Primes` enumerator and configures the tester
|
||||
with the largest remaining exponent. (`MersenneNumberDivisorByDivisorTester.cs`,
|
||||
lines 14-162.)
|
||||
* For each confirmed prime exponent, the CPU tester computes the largest divisor
|
||||
it needs to probe through `ComputeAllowedMaxDivisor`. If the range is non-empty
|
||||
it enters the `CheckDivisors` loop. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||
lines 83-110 and 161-266.)
|
||||
|
||||
## Inner loop behaviour (`CheckDivisors`)
|
||||
|
||||
The hot path is the `while (divisor <= limit)` loop in
|
||||
`MersenneNumberDivisorByDivisorCpuTester.CheckDivisors`.
|
||||
|
||||
1. The loop enumerates divisor candidates of the form `d_k = 2pk + 1` by
|
||||
incrementing `divisor` by `step = 2p` every iteration. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||
lines 177-256.)
|
||||
2. Small modulus filters track `d_k (mod 3·5·7·8·10·11)` to discard impossible
|
||||
candidates without touching the divisor-cycle cache. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||
lines 195-263.)
|
||||
3. For every admissible divisor the tester fetches its Montgomery data and the
|
||||
cached cycle length. When the cache does not contain an entry the
|
||||
implementation recomputes the cycle by factoring the divisor. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||
lines 223-254.)
|
||||
4. A cycle equal to the tested exponent proves divisibility, so the loop aborts
|
||||
as soon as one hit is found. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||
lines 241-247.)
|
||||
|
||||
`DivisorScanSession.CheckDivisor` follows the same `d_k` progression when a
|
||||
batch API is used. It keeps a running Montgomery residue via
|
||||
`ExponentRemainderStepper`, but the stepper currently recomputes the delta by
|
||||
calling a full `Pow2MontgomeryModWindowed` whenever the exponent jumps.
|
||||
(`MersenneNumberDivisorByDivisorCpuTester.cs`, lines 358-405 and
|
||||
`ExponentRemainderStepper.cs`, lines 26-95.)
|
||||
|
||||
## Where the analytic model fits
|
||||
|
||||
The formulas we established for the quotient sequence
|
||||
|
||||
\[
|
||||
\frac{Q_{k+1}}{Q_k} = \frac{2pk + 1}{2p(k+1) + 1}, \qquad
|
||||
Q_k - Q_{k+1} = \frac{N·2p}{(2pk + 1)(2p(k+1) + 1)}
|
||||
\]
|
||||
|
||||
tell us that the successive quotients and remainders shrink smoothly and that the
|
||||
per-step update depends only on the previous `(Q_k, r_k)` state and the next
|
||||
`d_{k+1}`. This lines up with several TODO comments that highlight missing
|
||||
incremental updates:
|
||||
|
||||
* `ExponentRemainderStepper` already caches the previous exponent and a running
|
||||
Montgomery residue, but it falls back to a full modular exponentiation to
|
||||
compute the delta between consecutive divisors. (`ExponentRemainderStepper.cs`,
|
||||
lines 49-72.)
|
||||
* `CheckDivisors` rebuilds the Montgomery data for every hit candidate even
|
||||
though the sequence of `d_k` values is strictly monotone.
|
||||
|
||||
The quotient identity gives us a way to compute `Δ_k = Q_k - Q_{k+1}` by reading
|
||||
`Q_k` and the inexpensive division `(2pQ_k - r_k) / d_{k+1}`. Once we know `Δ_k`
|
||||
we can update
|
||||
|
||||
\[
|
||||
Q_{k+1} = Q_k - Δ_k, \qquad r_{k+1} = r_k - 2p·Q_k + Δ_k · d_{k+1},
|
||||
\]
|
||||
|
||||
without ever evaluating `2^p mod d_{k+1}` from scratch. Incorporating this into
|
||||
`ExponentRemainderStepper` would let the CPU path reuse a single Montgomery state
|
||||
across the entire scan and swap the current delta exponentiation (which costs a
|
||||
full modular ladder) for one multiply-high with a precomputed reciprocal of
|
||||
`d_{k+1}`. The loop already maintains `d_k` and `step`, so the extra bookkeeping
|
||||
fits naturally.
|
||||
|
||||
## Optimization ideas grounded in the code
|
||||
|
||||
1. **Delta-driven Montgomery updates.** Extend `DivisorScanSession` so it tracks
|
||||
`(Q_k, r_k)` alongside the Montgomery residue and uses the analytic delta
|
||||
formula to derive the next residue. This replaces the per-step powmod in
|
||||
`ExponentRemainderStepper.ComputeNext` with a handful of 64-bit operations.
|
||||
(`MersenneNumberDivisorByDivisorCpuTester.cs`, lines 358-405;
|
||||
`ExponentRemainderStepper.cs`, lines 49-72.)
|
||||
2. **Bit-range skipping.** Because `Q_{k+1} / Q_k` is exactly the rational
|
||||
`(2pk + 1)/(2p(k+1) + 1)`, we can predict when `⌊log₂ Q_k⌋` loses a bit and
|
||||
jump the loop counter to the first `k` that crosses a target quotient bound
|
||||
instead of checking each divisor. This is compatible with the existing
|
||||
`allowedMax` guard because it only modifies how we arrive at the stopping
|
||||
divisor. (`MersenneNumberDivisorByDivisorCpuTester.cs`, lines 161-266.)
|
||||
3. **Reuse Montgomery metadata.** The loop already calls `MontgomeryDivisorDataCache.Get`
|
||||
for each divisor, but the cache lookup is repeated even though the next
|
||||
divisor is a small increment away. Persisting the previous `MontgomeryDivisorData`
|
||||
together with the `(Q_k, r_k)` tuple lets us skip redundant cache reads when
|
||||
the divisor stays within the hot range. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||
lines 223-256.)
|
||||
4. **Reciprocal-based delta evaluation.** `DivisorScanSession.TryComputeAnalyticHit` already keeps the `(Q_k, r_k)` tuple
|
||||
per exponent, but the current update loop advances one step at a time and divides by `nextDivisor` through `Int128`
|
||||
arithmetic. (`MersenneNumberDivisorByDivisorCpuTester.cs`, lines 531-594.) We can remove the per-iteration division by
|
||||
storing a 64-bit reciprocal of `d_k` alongside the state and updating it with a single Newton step when the divisor grows
|
||||
by `2p`. Once the reciprocal is known, `Δ_k` follows from a multiply-high of `2p·Q_k - r_k`. This turns the loop into a
|
||||
straight-line sequence of multiplies and subtractions and makes the state update viable even when we unroll multiple
|
||||
increments at once.
|
||||
5. **Carry the Montgomery residue forward.** `PrimeDivisorState` already tracks `MontgomeryResidue`, yet the analytic path
|
||||
currently recomputes it after the quotient update by calling `ToMontgomeryResidue`. (`MersenneNumberDivisorByDivisorCpuTester.cs`,
|
||||
lines 602-618.) We can instead reuse the residue by multiplying it with the precomputed `R^2 mod d_k` factor that the
|
||||
cache returns for every divisor. Because the divisor grows deterministically, we only need to fetch the updated Montgomery
|
||||
constants when `d_k` crosses a cache boundary, letting the fast path avoid modular reductions entirely.
|
||||
|
||||
These adjustments directly leverage the quotient progression we analysed and
|
||||
align with the existing TODO list, giving us a clear path to reduce the number of
|
||||
modular exponentiations and cache lookups in the CPU by-divisor path.
|
||||
|
||||
## Implementation status
|
||||
|
||||
* **Delta-driven Montgomery updates:** Implemented. The CPU session now keeps per-prime quotients, remainders, and last divisors so it can advance the analytic state across consecutive candidates, the reciprocal-corrected loop avoids per-step `UInt128` division, and zero cycle remainders short-circuit to direct Montgomery hits without invoking powmods.
|
||||
* **Bit-range skipping:** Implemented. The CPU divisor sweep now predicts when the quotient loses a bit across both 64-bit and wider strides by deriving the next jump with `UInt128` arithmetic,
|
||||
and it only falls back to single-step increments when a projected jump would overshoot the configured limit or exceed `ulong.MaxValue`.
|
||||
* **Reuse Montgomery metadata:** Implemented. Each prime state now persists the divisor's `n'`, `R`, and `R^2` factors so resynchronization seeds the exponent stepper directly, and divisor sessions rebuild the Montgomery tuple from the cached state before touching the shared cache, meaning cache lookups only occur when no stored snapshot matches the new divisor.
|
||||
* **Reciprocal-based delta evaluation:** Implemented. The analytic updater now retries with a direct 128-bit division when the reciprocal corrections exhaust their tolerance, refreshes the cached reciprocal from that exact quotient, and keeps the progression on the analytic path without rebuilding the state.
|
||||
* **Carry the Montgomery residue forward:** Implemented. Each analytic update now normalizes the refreshed remainder and multiplies it by the cached `R^2 mod d` factor immediately, so the exponent stepper always has a Montgomery residue ready without recomputing modular reductions.
|
||||
|
||||
|
||||
## Prime-order factoring bottleneck and planned fixes
|
||||
|
||||
The Pollard rho fallback in `PrimeOrderCalculator` still performs every quadratic
|
||||
polynomial advance by issuing a full-width division. The tight loop in
|
||||
`AdvancePolynomial` squares the iterate and takes a `% modulus`, which triggers
|
||||
hardware division for each step and dominates the runtime once we test large
|
||||
Mersenne divisors.【F:PerfectNumbers.Core/PrimeOrderCalculator.cs†L1180-L1211】
|
||||
The by-divisor CPU path consequently spends a measurable portion of its wall
|
||||
clock time inside the factorization helper whenever it needs to refresh a cycle
|
||||
or validate that a divisor candidate is prime.
|
||||
|
||||
To bring this code in line with the Montgomery-first approach we use elsewhere
|
||||
we should:
|
||||
|
||||
* **Introduce a Montgomery-backed polynomial step.** `PrimeDivisorState` already
|
||||
carries the full set of Montgomery constants (`n'`, `R`, `R^2`) for each
|
||||
divisor we touch in the by-divisor scan, and `DivisorScanSession` rebuilds a
|
||||
`MontgomeryDivisorData` snapshot whenever it seeds the analytic stepper or the
|
||||
exponent synchronizer.【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L645-L779】【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L1246-L1398】
|
||||
We can surface the same data to the Pollard rho helper and rewrite
|
||||
`AdvancePolynomial` so it accepts a precomputed reducer `(modulus, n', R^2)`.
|
||||
The update then becomes `x = MontgomerySquareAdd(x, cMontgomery)`—a multiply
|
||||
followed by one Montgomery reduction instead of a general-purpose remainder.
|
||||
This eliminates the `% modulus` call entirely and lets the CPU factorizer reuse
|
||||
the same fast path as the analytic delta updates.
|
||||
* **Reuse reciprocal estimates between neighbouring divisors.** The analytic path
|
||||
already stores a reciprocal for each divisor and refines it with Newton steps
|
||||
to divide by `d_k` without hitting the 128-bit fallback.【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L876-L939】
|
||||
Pollard rho can share the very same multiplier. We can stash the `FastDiv64`
|
||||
multiplier inside the Pollard rho state and update it when `q` grows by `2p`
|
||||
using a single Newton refinement, mirroring `DivideRoundUpUsingReciprocal`.
|
||||
Once the reciprocal is in place, the `(x * x + c) mod q` step reduces to a
|
||||
pair of `UInt128` products and one multiply-high to estimate the quotient—a
|
||||
drop-in use of `ULongExtensions.FastDiv64`.【F:PerfectNumbers.Core/ULongExtensions.cs†L69-L101】
|
||||
* **Warm-start the Montgomery context from the monotone `q` stream.** Each scan
|
||||
step increments the divisor by the fixed stride `2p`, so the cached Montgomery
|
||||
tuple only changes slowly. We can therefore hand the next divisor’s
|
||||
`MontgomeryDivisorData` directly to the Pollard rho routine instead of forcing
|
||||
it to recompute the reducer. This is already how `DivisorScanSession` avoids
|
||||
redundant cache lookups; exporting the cached tuple keeps the factorizer in
|
||||
lock-step with the rest of the pipeline.【F:PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs†L645-L779】
|
||||
|
||||
With these changes the divisor-refresh path would no longer spend most of its
|
||||
cycles on integer divisions, and the monotone growth of `q = 2kp + 1` for
|
||||
`p ≥ 138,000,000` would guarantee that the reciprocal refinements stay stable
|
||||
across thousands of consecutive candidates.
|
||||
|
||||
### Pollard rho factoring implementation status
|
||||
|
||||
* **Montgomery-backed polynomial step:** Completed. `PollardRhoMontgomeryReducer` now squares and adds in Montgomery space,
|
||||
and the Pollard rho loops in `PrimeOrderCalculator` and `MersenneDivisorCycles` reuse the shared reducer instead of issuing `%` operations.
|
||||
* **Reciprocal reuse:** Completed. The reducer tracks a cached FastDiv64 reciprocal, refines it for each monotone modulus, and exposes `AdvanceStandard` so the standard-domain iterates reuse the cached multiplier without `%` or `UInt128` division.
|
||||
* **Warm-started Montgomery context:** Completed. The reducer accepts precomputed `MontgomeryDivisorData` snapshots, letting callers seed Pollard rho directly from the cached divisor state when the by-divisor pipeline advances to the next `q`.
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
- implement full routing for CPU or GPU execution based on the (...)-device=cpu|gpu switches. The current --mersenne=bydivisor GPU flow appears to run every stage on the GPU and ignore switches such as --primes-device for the primality check.
|
||||
- investigate why the --mersenne=bydivisor GPU path runs out of host memory. Exceptions must remain visible.
|
||||
- Reimplement the GPU cycle heuristics for MersenneNumberIncrementalGpuTester and re-enable Scan_handles_various_prime_exponents once the device path is fixed.
|
||||
- Extend the GPU numeric type probe to cover proposed custom types such as GpuUInt256 and GpuUInt512 once their definitions are available so we can plan the wide-order heuristics on the device.
|
||||
- document the GPU by-divisor regressions introduced in commit cd1debe ("Various micro- and medium- optimizations"): the refactor dropped divisor-cycle caching, remainder stepper reuse, and honoring the cached cycle parameter so we can plan which pieces to restore.
|
||||
+589
@@ -0,0 +1,589 @@
|
||||
EvenPerfectBitScanner\Program.cs:69: bool useModuloWorkaround = false; // TODO: Remove once the runtime defaults to the ImmediateModulo path measured fastest in GpuUInt128NativeModuloBenchmarks.
|
||||
EvenPerfectBitScanner\Program.cs:78: bool useDivisorCycles = false; // TODO: Default this flag to true and remove the opt-out once divisor cycle lookups are mandatory per the optimized by-divisor benchmarks.
|
||||
EvenPerfectBitScanner\Program.cs:109: // TODO: Replace this ulong.Parse with the Utf8Parser-based span helper once CLI option parsing
|
||||
EvenPerfectBitScanner\Program.cs:124: // TODO: Swap int.Parse for the span-based Utf8Parser fast path to avoid transient strings when
|
||||
EvenPerfectBitScanner\Program.cs:133: kernelType = GpuKernelType.Pow2Mod; // TODO: Switch this mode to the ProcessEightBitWindows kernel once Pow2Minus1Mod adopts the benchmarked faster windowed ladder.
|
||||
EvenPerfectBitScanner\Program.cs:164: // TODO: Replace UInt128.Parse with the benchmarked fast-path parser once we expose the
|
||||
EvenPerfectBitScanner\Program.cs:177: // TODO: Replace ulong.TryParse with the Utf8Parser fast-path once CLI parsing utilities expose
|
||||
EvenPerfectBitScanner\Program.cs:186: // TODO: Swap this TryParse for the zero-allocation Utf8Parser helper to keep residue CLI option
|
||||
EvenPerfectBitScanner\Program.cs:211: // TODO: Use the Utf8Parser-based reader here to eliminate the temporary substring allocation
|
||||
EvenPerfectBitScanner\Program.cs:273: // TODO: Replace this TryParse with the Utf8Parser span helper once shared CLI parsing utilities
|
||||
EvenPerfectBitScanner\Program.cs:288: // TODO: Swap to the Utf8Parser double fast-path so zero-hard parsing avoids culture-dependent
|
||||
EvenPerfectBitScanner\Program.cs:304: // TODO: Replace these double/int TryParse calls with span-based Utf8Parser helpers once
|
||||
EvenPerfectBitScanner\Program.cs:338: // TODO: Convert this int.Parse to the Utf8Parser-based helper to keep CLI option parsing
|
||||
EvenPerfectBitScanner\Program.cs:344: // TODO: Swap int.Parse for Utf8Parser to align with the faster CLI numeric parsing path.
|
||||
EvenPerfectBitScanner\Program.cs:349: // TODO: Replace int.Parse with Utf8Parser-based parsing to eliminate temporary strings when
|
||||
EvenPerfectBitScanner\Program.cs:355: // TODO: Use the Utf8Parser-based fast path here to match the other CLI numeric parsing
|
||||
EvenPerfectBitScanner\Program.cs:361: // TODO: Inline the Utf8Parser-based int reader here once shared CLI parsing utilities land so
|
||||
EvenPerfectBitScanner\Program.cs:371: // TODO: Replace int.Parse with the shared Utf8Parser fast-path to keep hot CLI option parsing
|
||||
EvenPerfectBitScanner\Program.cs:385: // TODO: Convert this int.Parse to Utf8Parser once the optimized CLI parsing helper is shared
|
||||
EvenPerfectBitScanner\Program.cs:443: // TODO: Wire GenerateGpu to the unrolled-hex kernel that led the MersenneDivisorCycleLengthGpuBenchmarks once it lands.
|
||||
EvenPerfectBitScanner\Program.cs:464: // TODO: Keep a single cached block loaded from disk and honor the configured device when
|
||||
EvenPerfectBitScanner\Program.cs:468: // TODO: Stop reloading the full snapshot once the ad-hoc path streams results straight from
|
||||
EvenPerfectBitScanner\Program.cs:504: // TODO: Swap the underlying pow2mod kernels to ProcessEightBitWindows once Pow2Minus1Mod migrates to the windowed helper highlighted in GpuPow2ModBenchmarks.
|
||||
EvenPerfectBitScanner\Program.cs:539: _byDivisorTester.UseDivisorCycles = useDivisorCycles; // TODO: Collapse this assignment once the runtime enforces divisor-cycle acceleration for every by-divisor scan path.
|
||||
EvenPerfectBitScanner\Program.cs:621: // TODO: Replace this File.WriteAllText call with the pooled TextFileWriter pipeline once the
|
||||
EvenPerfectBitScanner\Program.cs:642: // TODO: Rent this filter buffer from ArrayPool<ulong> and reuse it across reload batches so
|
||||
EvenPerfectBitScanner\Program.cs:717: // TODO: Rent this buffer from ArrayPool once the pooled prime-block allocator lands so worker
|
||||
EvenPerfectBitScanner\Program.cs:800: // TODO: Swap this char-by-char parser for the benchmarked Utf8Parser-based fast-path once
|
||||
EvenPerfectBitScanner\Program.cs:818: // TODO: Swap ulong.TryParse for the Utf8Parser-based fast path once the shared span helpers
|
||||
EvenPerfectBitScanner\Program.cs:837: // TODO: Inline this helper by invoking MersenneNumberDivisorByDivisorTester.Run directly at call sites so
|
||||
EvenPerfectBitScanner\Program.cs:885: // TODO: Replace this ulong.Parse call with the Utf8Parser-based span fast-path we benchmarked so results
|
||||
EvenPerfectBitScanner\Program.cs:905: // TODO: Replace these bool.TryParse calls with the span-based fast path once we expose the
|
||||
EvenPerfectBitScanner\Program.cs:974: // TODO: Replace this string interpolation with the pooled ValueStringBuilder pipeline from the
|
||||
EvenPerfectBitScanner\Program.cs:997: // TODO: Inline the fast-path transform here once we collapse the delegate* indirection so
|
||||
EvenPerfectBitScanner\Program.cs:1076: // TODO: Switch this StringBuilder-based formatter to a span-friendly Utf8Formatter pipeline so
|
||||
EvenPerfectBitScanner\Program.cs:1137: // TODO: Replace these per-flush FileStream/StreamWriter allocations with a pooled TextFileWriter-style
|
||||
EvenPerfectBitScanner\Program.cs:1155: // TODO: Remove this wrapper once callers can invoke BitOperations.PopCount directly so the hot bit-stat path
|
||||
EvenPerfectBitScanner\Program.cs:1310: // TODO: Remove this pass-through once callers can provide the full out parameters
|
||||
EvenPerfectBitScanner\Program.cs:1388: // TODO: Route by-divisor scans through the ProcessEightBitWindows pow2mod kernel once the windowed Pow2Minus1Mod helper replaces the slower single-bit ladder.
|
||||
EvenPerfectBitScanner\Program.cs:1394: // TODO: Adopt the windowed Pow2Minus1Mod helper here so divisor checks benefit from the ProcessEightBitWindows speedups.
|
||||
EvenPerfectBitScanner\Program.cs:1406: // TODO: Integrate the divisor-cycle cache here so the small-prime sweep reuses precomputed remainders instead of
|
||||
EvenPerfectBitScanner\Program.cs:1462: // TODO: Replace this byte-by-byte scan with the lookup-table based statistics collector validated in the
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:154: // TODO: Replace TryParse with the Utf8Parser-based span helper once the results parser accepts ReadOnlySpan<char>
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:166: // TODO: Switch to the Utf8Parser-based fast path so integer arguments avoid transient strings while parsing.
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:192: // TODO: Swap this string slicing for ReadOnlySpan<char>-based parsing once the CLI helpers expose
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:234: // TODO: Replace this vanilla StreamReader with a pooled FileStreamOptions + ArrayPool-backed reader so large
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:250: // TODO: Stream candidates through a pooled buffer pipeline so we can filter and dispatch without allocating
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:317: // TODO: Replace ulong.Parse with the Utf8Parser-based span helper so CSV reload stays on the zero-allocation
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:325: // TODO: Swap bool.Parse for the Utf8Parser-powered boolean reader once exposed so reload avoids allocating
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:333: // TODO: Switch this path composition to Span<char>-based stack buffers once we expose the pooled
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:349: // TODO: Switch WriteCsv to a span-based Utf8Formatter pipeline backed by ArrayPool-rented buffers so
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:358: // TODO: Replace this per-entry WriteLine with a batched chunk writer that reuses pooled
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:380: // TODO: Replace the Task-based scheduling with the pooled work queue used in the scanner so candidate
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:388: // TODO: Rent this line buffer from ArrayPool<string> to eliminate repeated allocations when scanning
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:452: // TODO: Rent this full-capacity buffer from ArrayPool<string> and reuse it between reads instead
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:458: // TODO: Rent the partial chunk buffer from ArrayPool<string> instead of allocating a fresh
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:499: // TODO: Rent the candidate array from ArrayPool<CandidateResult> so chunk processing remains allocation-free
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:531: // TODO: Replace this copy with an in-place filtering pipeline that reuses pooled buffers; duplicating the list
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:537: // TODO: Keep using the Open.Numeric prime enumerator for the merge walk but hoist a shared instance so reloads
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:578: // TODO: Replace this Dictionary allocation with the pooled span-based frequency map once the ValueListBuilder
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:593: // TODO: Rent the raw prime accumulator from ArrayPool<List<CandidateResult>> or migrate to a pooled struct
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:661: // TODO: Rent these segment arrays from ArrayPool<List<CandidateResult>> so parallel splitting avoids allocating
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:666: // TODO: Rent the tasks array from ArrayPool<Task> so repeated reloads avoid allocating new
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:670: // TODO: Replace the separate division/modulo here with Math.DivRem (or the branchless chunk
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:690: // TODO: Replace Task.Run with the pooled work queue from the scanner so scheduling
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:696: // TODO: Rent these per-worker lists from a dedicated pool so parallel splits reuse
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:721: // TODO: Promote these final result lists to pooled builders so we can reuse the buffers
|
||||
PerfectNumbers.Core.Tests\AlphaCacheTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
EvenPerfectBitScanner.Benchmarks\GpuUInt128Montgomery64Benchmarks.cs:50: // TODO: Migrate remaining callers of MulModMontgomery64 to the extension helper; the struct
|
||||
PerfectNumbers.Core.Tests\AlphaCalculationsTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
EvenPerfectBitScanner.Benchmarks\GpuUInt128MulModBenchmarks.cs:52: // TODO: Switch the extension method to allocate per iteration, as it's faster. Keep the benchmarks.
|
||||
PerfectNumbers.Core\AlphaCache.cs:11: // TODO: Replace this dictionary lookup with a lock-free cache that reuses pooled AlphaValues so alpha requests avoid
|
||||
PerfectNumbers.Core\AlphaCache.cs:26: // TODO: Return pooled AlphaValues instances to a shared cache before clearing so repeated warm-ups do not thrash the
|
||||
PerfectNumbers.Core\AlphaCalculations.cs:11: // TODO: Reuse pooled numerator/denominator buffers here so the alphaP computation stops allocating
|
||||
PerfectNumbers.Core\AlphaCalculations.cs:20: // TODO: Reuse pooled ERational builders here so repeated factor computations pull precomputed
|
||||
PerfectNumbers.Core\AlphaCalculations.cs:31: // TODO: Replace the repeated Multiply calls with a span-based aggregator that batches factors,
|
||||
EvenPerfectBitScanner.Benchmarks\GpuUInt128MulModByLimbBenchmarks.cs:59: // TODO: Switch the extension method back to legacy method, as it's faster. Keep the benchmarks.
|
||||
PerfectNumbers.Core\AlphaMScannerCandidateStatus.cs:5: // TODO: Store these statuses as byte-coded constants so hot-path evaluations can stay branchless when scanning candidates.
|
||||
EvenPerfectBitScanner.Benchmarks\GpuUInt128NativeModuloBenchmarks.cs:49: // TODO: Drop MulModWithNativeModulo from production once all callers use the immediate
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:28: // TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:42: // TODO: Swap this `%` for the shared divisor-cycle remainder helper so initialization reuses the cached
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:72: // TODO: Replace this `%` with the UInt128-aware cycle reducer so large deltas use the cached subtraction
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:85: // TODO: Route this `%` through the shared divisor-cycle helper so repeated wrap-arounds avoid
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:96: // TODO: Expose these fields directly once the residue scanners adopt the single-cycle helper
|
||||
PerfectNumbers.Core.Tests\InputParserTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
PerfectNumbers.Core.Tests\KRangeFinderTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:63: private const int CycleGenerationBatchSize = 262_144; // TODO: Remove the block-sized batch once the single-cycle generator
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:69: private readonly object _sync = new(); // TODO: Delete the synchronization object once the cache exposes only snapshot reads and single-cycle computation so callers no longer contend on locks.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:70: private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new(); // TODO: Drop the concurrent dictionary entirely when the block pipeline is replaced with single-cycle generation; the new plan never queues background work.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:71: private readonly ConcurrentDictionary<Accelerator, Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance); // TODO: Swap this ConcurrentDictionary for a simple accelerator-indexed array once the single-cycle helper precompiles kernels during startup, eliminating the need for thread-safe lookups.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:73: private CycleBlock? _activeBlock; // TODO: Remove dynamic blocks altogether once lookups route to the snapshot or compute an individual cycle without mutating shared state.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:74: private CycleBlock? _prefetchedBlock; // TODO: Delete prefetch support so the cache never materializes extra blocks and callers always compute the missing cycle inline.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:89: // TODO: Remove the ability to reload at runtime once the cache becomes a read-only snapshot plus
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:103: // TODO: Remove the lock once single-cycle generation eliminates shared mutable state; a simple
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:125: // TODO: Remove the dictionary once prefetching and background block tracking disappear; with single-cycle generation we will keep only the snapshot and skip additional bookkeeping entirely.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:139: // TODO: Replace this block-based acquisition with a direct GetCycle API that computes the missing cycle synchronously on the configured device so the cache never hands out mutable blocks or requires disposal.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:160: // TODO: Replace Task.Run with an inline single-cycle computation that runs on the configured
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:179: // TODO: Stop promoting freshly generated blocks into the shared cache and, instead of
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:227: StartPrefetchLocked(prefetched.Index + 1); // TODO: Remove this call once prefetching is dropped
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:253: StartPrefetchLocked(block.Index + 1); // TODO: Remove once prefetching is removed and the cache
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:259: // TODO: Remove prefetching entirely so the cache maintains a single snapshot block and computes
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:283: // TODO: Delete this once prefetching is removed; single-cycle generation will happen inline and
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:292: // TODO: Delete this callback when we remove prefetching. Single-cycle generation will run inline,
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:309: // TODO: Remove this entire method when prefetching goes away. The single-cycle flow should never
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:391: // TODO: Replace this block generator with a single-cycle helper that reuses shared buffers,
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:424: // TODO: Port this CPU fallback to the unrolled-hex cycle generator once it is shared so misses outside the snapshot
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:440: // TODO: Once single-cycle generation is in place, bypass this block-sized kernel entirely and
|
||||
PerfectNumbers.Core\EulerPrimeTester.cs:17: // TODO: Inline this helper into callers so we skip the wrapper and pass the divisibility check directly to the optimized
|
||||
PerfectNumbers.Core\Cpu\CpuConstants.cs:9: // TODO: Generate these masks at startup from the benchmarked Mod10 automaton tables so CPU filtering stays aligned with
|
||||
PerfectNumbers.Core\Cpu\CpuConstants.cs:11: // TODO: Fold in the Mod6ComparisonBenchmarks stride table here so callers can merge the mod 6 skips with these masks and
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:47: // TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:57: // TODO: Once divisor cycle lengths are mandatory, pull the delta multiplier from the
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:60: // TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:78: // TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:87: // TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:89: // TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:124: // TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:133: // TODO: Once the divisor-cycle cache exposes a direct Montgomery delta, multiply it here instead
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:21: set => _useDivisorCycles = value; // TODO: Delete the toggle once divisor cycle data is always consulted so CPU scans never fall back to the slower pure-Montgomery path.
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:194: // TODO: Hoist MontgomeryDivisorData acquisition into the divisor-cycle cache so we reuse the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:203: // TODO: Swap this block lease for the direct single-cycle lookup once the cache exposes it
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207: // TODO: When divisorCycle is zero (cache miss), compute only that single cycle on the device
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||
PerfectNumbers.Core\IMersenneNumberDivisorByDivisorTester.cs:7: bool UseDivisorCycles { get; set; } // TODO: Remove the setter once divisor cycle usage becomes mandatory so all implementations always leverage the cached cycles.
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberLucasLehmerCpuTester.cs:11: // TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberLucasLehmerCpuTester.cs:34: // TODO: Port this Lucas–Lehmer powmod to the ProcessEightBitWindows helper so the CPU
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:15: // TODO: Wire this residue scan into DivisorCycleCache so the mandatory cycle acceleration from the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:17: // TODO: When a required cycle is missing from the snapshot, compute only that single cycle on the device
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:91: // TODO: Once cycle data is exposed for residue scans, consult the cached divisor cycle here
|
||||
PerfectNumbers.Core\InputParser.cs:13: // TODO: Replace Split/Parse with the span-based exponent parser so BigInteger inputs avoid allocations
|
||||
PerfectNumbers.Core\InputParser.cs:20: // TODO: Replace BigInteger.Parse(string) with the span overload once callers provide ReadOnlySpan<char> so
|
||||
PerfectNumbers.Core\InputParser.cs:27: // TODO: Swap decimal.Parse for the Utf8Parser-based decimal reader so callers avoid culture-aware conversions
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:30: // TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:43: // TODO: Swap these Mod3/Mod5 zero checks to the cached residue tables once the automaton exposes
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:53: // TODO: Swap this PowModWithCycle call for the ProcessEightBitWindows helper once
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:65: // TODO: Route the phi-based powmods through the upcoming windowed helper to
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:70: // TODO: Once the windowed pow2 helper is available expose a version
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:77: // TODO: Replace this divisor powmod with the shared windowed
|
||||
PerfectNumbers.Core\Gpu\GpuConstants.cs:7: // TODO: Auto-tune these GPU constants per accelerator so batch sizing aligns with the fastest kernel
|
||||
todo_list.txt:1:EvenPerfectBitScanner\Program.cs:69: bool useModuloWorkaround = false; // TODO: Remove once the runtime defaults to the ImmediateModulo path measured fastest in GpuUInt128NativeModuloBenchmarks.
|
||||
todo_list.txt:2:EvenPerfectBitScanner\Program.cs:78: bool useDivisorCycles = false; // TODO: Default this flag to true and remove the opt-out once divisor cycle lookups are mandatory per the optimized by-divisor benchmarks.
|
||||
todo_list.txt:3:EvenPerfectBitScanner\Program.cs:109: // TODO: Replace this ulong.Parse with the Utf8Parser-based span helper once CLI option parsing
|
||||
todo_list.txt:4:EvenPerfectBitScanner\Program.cs:124: // TODO: Swap int.Parse for the span-based Utf8Parser fast path to avoid transient strings when
|
||||
todo_list.txt:5:EvenPerfectBitScanner\Program.cs:133: kernelType = GpuKernelType.Pow2Mod; // TODO: Switch this mode to the ProcessEightBitWindows kernel once Pow2Minus1Mod adopts the benchmarked faster windowed ladder.
|
||||
todo_list.txt:6:EvenPerfectBitScanner\Program.cs:164: // TODO: Replace UInt128.Parse with the benchmarked fast-path parser once we expose the
|
||||
todo_list.txt:7:EvenPerfectBitScanner\Program.cs:177: // TODO: Replace ulong.TryParse with the Utf8Parser fast-path once CLI parsing utilities expose
|
||||
todo_list.txt:8:EvenPerfectBitScanner\Program.cs:186: // TODO: Swap this TryParse for the zero-allocation Utf8Parser helper to keep residue CLI option
|
||||
todo_list.txt:9:EvenPerfectBitScanner\Program.cs:211: // TODO: Use the Utf8Parser-based reader here to eliminate the temporary substring allocation
|
||||
todo_list.txt:10:EvenPerfectBitScanner\Program.cs:273: // TODO: Replace this TryParse with the Utf8Parser span helper once shared CLI parsing utilities
|
||||
todo_list.txt:11:EvenPerfectBitScanner\Program.cs:288: // TODO: Swap to the Utf8Parser double fast-path so zero-hard parsing avoids culture-dependent
|
||||
todo_list.txt:12:EvenPerfectBitScanner\Program.cs:304: // TODO: Replace these double/int TryParse calls with span-based Utf8Parser helpers once
|
||||
todo_list.txt:13:EvenPerfectBitScanner\Program.cs:338: // TODO: Convert this int.Parse to the Utf8Parser-based helper to keep CLI option parsing
|
||||
todo_list.txt:14:EvenPerfectBitScanner\Program.cs:344: // TODO: Swap int.Parse for Utf8Parser to align with the faster CLI numeric parsing path.
|
||||
todo_list.txt:15:EvenPerfectBitScanner\Program.cs:349: // TODO: Replace int.Parse with Utf8Parser-based parsing to eliminate temporary strings when
|
||||
todo_list.txt:16:EvenPerfectBitScanner\Program.cs:355: // TODO: Use the Utf8Parser-based fast path here to match the other CLI numeric parsing
|
||||
todo_list.txt:17:EvenPerfectBitScanner\Program.cs:361: // TODO: Inline the Utf8Parser-based int reader here once shared CLI parsing utilities land so
|
||||
todo_list.txt:18:EvenPerfectBitScanner\Program.cs:371: // TODO: Replace int.Parse with the shared Utf8Parser fast-path to keep hot CLI option parsing
|
||||
todo_list.txt:19:EvenPerfectBitScanner\Program.cs:385: // TODO: Convert this int.Parse to Utf8Parser once the optimized CLI parsing helper is shared
|
||||
todo_list.txt:20:EvenPerfectBitScanner\Program.cs:443: // TODO: Wire GenerateGpu to the unrolled-hex kernel that led the MersenneDivisorCycleLengthGpuBenchmarks once it lands.
|
||||
todo_list.txt:21:EvenPerfectBitScanner\Program.cs:464: // TODO: Keep a single cached block loaded from disk and honor the configured device when
|
||||
todo_list.txt:22:EvenPerfectBitScanner\Program.cs:468: // TODO: Stop reloading the full snapshot once the ad-hoc path streams results straight from
|
||||
todo_list.txt:23:EvenPerfectBitScanner\Program.cs:504: // TODO: Swap the underlying pow2mod kernels to ProcessEightBitWindows once Pow2Minus1Mod migrates to the windowed helper highlighted in GpuPow2ModBenchmarks.
|
||||
todo_list.txt:24:EvenPerfectBitScanner\Program.cs:539: _byDivisorTester.UseDivisorCycles = useDivisorCycles; // TODO: Collapse this assignment once the runtime enforces divisor-cycle acceleration for every by-divisor scan path.
|
||||
todo_list.txt:25:EvenPerfectBitScanner\Program.cs:621: // TODO: Replace this File.WriteAllText call with the pooled TextFileWriter pipeline once the
|
||||
todo_list.txt:26:EvenPerfectBitScanner\Program.cs:642: // TODO: Rent this filter buffer from ArrayPool<ulong> and reuse it across reload batches so
|
||||
todo_list.txt:27:EvenPerfectBitScanner\Program.cs:717: // TODO: Rent this buffer from ArrayPool once the pooled prime-block allocator lands so worker
|
||||
todo_list.txt:28:EvenPerfectBitScanner\Program.cs:800: // TODO: Swap this char-by-char parser for the benchmarked Utf8Parser-based fast-path once
|
||||
todo_list.txt:29:EvenPerfectBitScanner\Program.cs:818: // TODO: Swap ulong.TryParse for the Utf8Parser-based fast path once the shared span helpers
|
||||
todo_list.txt:30:EvenPerfectBitScanner\Program.cs:837: // TODO: Inline this helper by invoking MersenneNumberDivisorByDivisorTester.Run directly at call sites so
|
||||
todo_list.txt:31:EvenPerfectBitScanner\Program.cs:885: // TODO: Replace this ulong.Parse call with the Utf8Parser-based span fast-path we benchmarked so results
|
||||
todo_list.txt:32:EvenPerfectBitScanner\Program.cs:905: // TODO: Replace these bool.TryParse calls with the span-based fast path once we expose the
|
||||
todo_list.txt:33:EvenPerfectBitScanner\Program.cs:974: // TODO: Replace this string interpolation with the pooled ValueStringBuilder pipeline from the
|
||||
todo_list.txt:34:EvenPerfectBitScanner\Program.cs:997: // TODO: Inline the fast-path transform here once we collapse the delegate* indirection so
|
||||
todo_list.txt:35:EvenPerfectBitScanner\Program.cs:1076: // TODO: Switch this StringBuilder-based formatter to a span-friendly Utf8Formatter pipeline so
|
||||
todo_list.txt:36:EvenPerfectBitScanner\Program.cs:1137: // TODO: Replace these per-flush FileStream/StreamWriter allocations with a pooled TextFileWriter-style
|
||||
todo_list.txt:37:EvenPerfectBitScanner\Program.cs:1155: // TODO: Remove this wrapper once callers can invoke BitOperations.PopCount directly so the hot bit-stat path
|
||||
todo_list.txt:38:EvenPerfectBitScanner\Program.cs:1310: // TODO: Remove this pass-through once callers can provide the full out parameters
|
||||
todo_list.txt:39:EvenPerfectBitScanner\Program.cs:1388: // TODO: Route by-divisor scans through the ProcessEightBitWindows pow2mod kernel once the windowed Pow2Minus1Mod helper replaces the slower single-bit ladder.
|
||||
todo_list.txt:40:EvenPerfectBitScanner\Program.cs:1394: // TODO: Adopt the windowed Pow2Minus1Mod helper here so divisor checks benefit from the ProcessEightBitWindows speedups.
|
||||
todo_list.txt:41:EvenPerfectBitScanner\Program.cs:1406: // TODO: Integrate the divisor-cycle cache here so the small-prime sweep reuses precomputed remainders instead of
|
||||
todo_list.txt:42:EvenPerfectBitScanner\Program.cs:1462: // TODO: Replace this byte-by-byte scan with the lookup-table based statistics collector validated in the
|
||||
todo_list.txt:43:EvenPerfectBitScanner.ResultsParser\Program.cs:154: // TODO: Replace TryParse with the Utf8Parser-based span helper once the results parser accepts ReadOnlySpan<char>
|
||||
todo_list.txt:44:EvenPerfectBitScanner.ResultsParser\Program.cs:166: // TODO: Switch to the Utf8Parser-based fast path so integer arguments avoid transient strings while parsing.
|
||||
todo_list.txt:45:EvenPerfectBitScanner.ResultsParser\Program.cs:192: // TODO: Swap this string slicing for ReadOnlySpan<char>-based parsing once the CLI helpers expose
|
||||
todo_list.txt:46:EvenPerfectBitScanner.ResultsParser\Program.cs:234: // TODO: Replace this vanilla StreamReader with a pooled FileStreamOptions + ArrayPool-backed reader so large
|
||||
todo_list.txt:47:EvenPerfectBitScanner.ResultsParser\Program.cs:250: // TODO: Stream candidates through a pooled buffer pipeline so we can filter and dispatch without allocating
|
||||
todo_list.txt:48:EvenPerfectBitScanner.ResultsParser\Program.cs:317: // TODO: Replace ulong.Parse with the Utf8Parser-based span helper so CSV reload stays on the zero-allocation
|
||||
todo_list.txt:49:EvenPerfectBitScanner.ResultsParser\Program.cs:325: // TODO: Swap bool.Parse for the Utf8Parser-powered boolean reader once exposed so reload avoids allocating
|
||||
todo_list.txt:50:EvenPerfectBitScanner.ResultsParser\Program.cs:333: // TODO: Switch this path composition to Span<char>-based stack buffers once we expose the pooled
|
||||
todo_list.txt:51:EvenPerfectBitScanner.ResultsParser\Program.cs:349: // TODO: Switch WriteCsv to a span-based Utf8Formatter pipeline backed by ArrayPool-rented buffers so
|
||||
todo_list.txt:52:EvenPerfectBitScanner.ResultsParser\Program.cs:358: // TODO: Replace this per-entry WriteLine with a batched chunk writer that reuses pooled
|
||||
todo_list.txt:53:EvenPerfectBitScanner.ResultsParser\Program.cs:380: // TODO: Replace the Task-based scheduling with the pooled work queue used in the scanner so candidate
|
||||
todo_list.txt:54:EvenPerfectBitScanner.ResultsParser\Program.cs:388: // TODO: Rent this line buffer from ArrayPool<string> to eliminate repeated allocations when scanning
|
||||
todo_list.txt:55:EvenPerfectBitScanner.ResultsParser\Program.cs:452: // TODO: Rent this full-capacity buffer from ArrayPool<string> and reuse it between reads instead
|
||||
todo_list.txt:56:EvenPerfectBitScanner.ResultsParser\Program.cs:458: // TODO: Rent the partial chunk buffer from ArrayPool<string> instead of allocating a fresh
|
||||
todo_list.txt:57:EvenPerfectBitScanner.ResultsParser\Program.cs:499: // TODO: Rent the candidate array from ArrayPool<CandidateResult> so chunk processing remains allocation-free
|
||||
todo_list.txt:58:EvenPerfectBitScanner.ResultsParser\Program.cs:531: // TODO: Replace this copy with an in-place filtering pipeline that reuses pooled buffers; duplicating the list
|
||||
todo_list.txt:59:EvenPerfectBitScanner.ResultsParser\Program.cs:537: // TODO: Keep using the Open.Numeric prime enumerator for the merge walk but hoist a shared instance so reloads
|
||||
todo_list.txt:60:EvenPerfectBitScanner.ResultsParser\Program.cs:578: // TODO: Replace this Dictionary allocation with the pooled span-based frequency map once the ValueListBuilder
|
||||
todo_list.txt:61:EvenPerfectBitScanner.ResultsParser\Program.cs:593: // TODO: Rent the raw prime accumulator from ArrayPool<List<CandidateResult>> or migrate to a pooled struct
|
||||
todo_list.txt:62:EvenPerfectBitScanner.ResultsParser\Program.cs:661: // TODO: Rent these segment arrays from ArrayPool<List<CandidateResult>> so parallel splitting avoids allocating
|
||||
todo_list.txt:63:EvenPerfectBitScanner.ResultsParser\Program.cs:666: // TODO: Rent the tasks array from ArrayPool<Task> so repeated reloads avoid allocating new
|
||||
todo_list.txt:64:EvenPerfectBitScanner.ResultsParser\Program.cs:670: // TODO: Replace the separate division/modulo here with Math.DivRem (or the branchless chunk
|
||||
todo_list.txt:65:EvenPerfectBitScanner.ResultsParser\Program.cs:690: // TODO: Replace Task.Run with the pooled work queue from the scanner so scheduling
|
||||
todo_list.txt:66:EvenPerfectBitScanner.ResultsParser\Program.cs:696: // TODO: Rent these per-worker lists from a dedicated pool so parallel splits reuse
|
||||
todo_list.txt:67:EvenPerfectBitScanner.ResultsParser\Program.cs:721: // TODO: Promote these final result lists to pooled builders so we can reuse the buffers
|
||||
todo_list.txt:68:PerfectNumbers.Core.Tests\AlphaCacheTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
todo_list.txt:69:EvenPerfectBitScanner.Benchmarks\GpuUInt128Montgomery64Benchmarks.cs:50: // TODO: Migrate remaining callers of MulModMontgomery64 to the extension helper; the struct
|
||||
todo_list.txt:70:PerfectNumbers.Core.Tests\AlphaCalculationsTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
todo_list.txt:71:EvenPerfectBitScanner.Benchmarks\GpuUInt128MulModBenchmarks.cs:52: // TODO: Switch the extension method to allocate per iteration, as it's faster. Keep the benchmarks.
|
||||
todo_list.txt:72:PerfectNumbers.Core\AlphaCache.cs:11: // TODO: Replace this dictionary lookup with a lock-free cache that reuses pooled AlphaValues so alpha requests avoid
|
||||
todo_list.txt:73:PerfectNumbers.Core\AlphaCache.cs:26: // TODO: Return pooled AlphaValues instances to a shared cache before clearing so repeated warm-ups do not thrash the
|
||||
todo_list.txt:74:PerfectNumbers.Core\AlphaCalculations.cs:11: // TODO: Reuse pooled numerator/denominator buffers here so the alphaP computation stops allocating
|
||||
todo_list.txt:75:PerfectNumbers.Core\AlphaCalculations.cs:20: // TODO: Reuse pooled ERational builders here so repeated factor computations pull precomputed
|
||||
todo_list.txt:76:PerfectNumbers.Core\AlphaCalculations.cs:31: // TODO: Replace the repeated Multiply calls with a span-based aggregator that batches factors,
|
||||
todo_list.txt:77:EvenPerfectBitScanner.Benchmarks\GpuUInt128MulModByLimbBenchmarks.cs:59: // TODO: Switch the extension method back to legacy method, as it's faster. Keep the benchmarks.
|
||||
todo_list.txt:78:PerfectNumbers.Core\AlphaMScannerCandidateStatus.cs:5: // TODO: Store these statuses as byte-coded constants so hot-path evaluations can stay branchless when scanning candidates.
|
||||
todo_list.txt:79:EvenPerfectBitScanner.Benchmarks\GpuUInt128NativeModuloBenchmarks.cs:49: // TODO: Drop MulModWithNativeModulo from production once all callers use the immediate
|
||||
todo_list.txt:80:PerfectNumbers.Core\CycleRemainderStepper.cs:28: // TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
|
||||
todo_list.txt:81:PerfectNumbers.Core\CycleRemainderStepper.cs:42: // TODO: Swap this `%` for the shared divisor-cycle remainder helper so initialization reuses the cached
|
||||
todo_list.txt:82:PerfectNumbers.Core\CycleRemainderStepper.cs:72: // TODO: Replace this `%` with the UInt128-aware cycle reducer so large deltas use the cached subtraction
|
||||
todo_list.txt:83:PerfectNumbers.Core\CycleRemainderStepper.cs:85: // TODO: Route this `%` through the shared divisor-cycle helper so repeated wrap-arounds avoid
|
||||
todo_list.txt:84:PerfectNumbers.Core\CycleRemainderStepper.cs:96: // TODO: Expose these fields directly once the residue scanners adopt the single-cycle helper
|
||||
todo_list.txt:85:PerfectNumbers.Core.Tests\InputParserTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
todo_list.txt:86:PerfectNumbers.Core.Tests\KRangeFinderTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
todo_list.txt:87:PerfectNumbers.Core\DivisorCycleCache.cs:63: private const int CycleGenerationBatchSize = 262_144; // TODO: Remove the block-sized batch once the single-cycle generator
|
||||
todo_list.txt:88:PerfectNumbers.Core\DivisorCycleCache.cs:69: private readonly object _sync = new(); // TODO: Delete the synchronization object once the cache exposes only snapshot reads and single-cycle computation so callers no longer contend on locks.
|
||||
todo_list.txt:89:PerfectNumbers.Core\DivisorCycleCache.cs:70: private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new(); // TODO: Drop the concurrent dictionary entirely when the block pipeline is replaced with single-cycle generation; the new plan never queues background work.
|
||||
todo_list.txt:90:PerfectNumbers.Core\DivisorCycleCache.cs:71: private readonly ConcurrentDictionary<Accelerator, Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance); // TODO: Swap this ConcurrentDictionary for a simple accelerator-indexed array once the single-cycle helper precompiles kernels during startup, eliminating the need for thread-safe lookups.
|
||||
todo_list.txt:91:PerfectNumbers.Core\DivisorCycleCache.cs:73: private CycleBlock? _activeBlock; // TODO: Remove dynamic blocks altogether once lookups route to the snapshot or compute an individual cycle without mutating shared state.
|
||||
todo_list.txt:92:PerfectNumbers.Core\DivisorCycleCache.cs:74: private CycleBlock? _prefetchedBlock; // TODO: Delete prefetch support so the cache never materializes extra blocks and callers always compute the missing cycle inline.
|
||||
todo_list.txt:93:PerfectNumbers.Core\DivisorCycleCache.cs:89: // TODO: Remove the ability to reload at runtime once the cache becomes a read-only snapshot plus
|
||||
todo_list.txt:94:PerfectNumbers.Core\DivisorCycleCache.cs:103: // TODO: Remove the lock once single-cycle generation eliminates shared mutable state; a simple
|
||||
todo_list.txt:95:PerfectNumbers.Core\DivisorCycleCache.cs:125: // TODO: Remove the dictionary once prefetching and background block tracking disappear; with single-cycle generation we will keep only the snapshot and skip additional bookkeeping entirely.
|
||||
todo_list.txt:96:PerfectNumbers.Core\DivisorCycleCache.cs:139: // TODO: Replace this block-based acquisition with a direct GetCycle API that computes the missing cycle synchronously on the configured device so the cache never hands out mutable blocks or requires disposal.
|
||||
todo_list.txt:97:PerfectNumbers.Core\DivisorCycleCache.cs:160: // TODO: Replace Task.Run with an inline single-cycle computation that runs on the configured
|
||||
todo_list.txt:98:PerfectNumbers.Core\DivisorCycleCache.cs:179: // TODO: Stop promoting freshly generated blocks into the shared cache and, instead of
|
||||
todo_list.txt:99:PerfectNumbers.Core\DivisorCycleCache.cs:227: StartPrefetchLocked(prefetched.Index + 1); // TODO: Remove this call once prefetching is dropped
|
||||
todo_list.txt:100:PerfectNumbers.Core\DivisorCycleCache.cs:253: StartPrefetchLocked(block.Index + 1); // TODO: Remove once prefetching is removed and the cache
|
||||
todo_list.txt:101:PerfectNumbers.Core\DivisorCycleCache.cs:259: // TODO: Remove prefetching entirely so the cache maintains a single snapshot block and computes
|
||||
todo_list.txt:102:PerfectNumbers.Core\DivisorCycleCache.cs:283: // TODO: Delete this once prefetching is removed; single-cycle generation will happen inline and
|
||||
todo_list.txt:103:PerfectNumbers.Core\DivisorCycleCache.cs:292: // TODO: Delete this callback when we remove prefetching. Single-cycle generation will run inline,
|
||||
todo_list.txt:104:PerfectNumbers.Core\DivisorCycleCache.cs:309: // TODO: Remove this entire method when prefetching goes away. The single-cycle flow should never
|
||||
todo_list.txt:105:PerfectNumbers.Core\DivisorCycleCache.cs:391: // TODO: Replace this block generator with a single-cycle helper that reuses shared buffers,
|
||||
todo_list.txt:106:PerfectNumbers.Core\DivisorCycleCache.cs:424: // TODO: Port this CPU fallback to the unrolled-hex cycle generator once it is shared so misses outside the snapshot
|
||||
todo_list.txt:107:PerfectNumbers.Core\DivisorCycleCache.cs:440: // TODO: Once single-cycle generation is in place, bypass this block-sized kernel entirely and
|
||||
todo_list.txt:108:PerfectNumbers.Core\EulerPrimeTester.cs:17: // TODO: Inline this helper into callers so we skip the wrapper and pass the divisibility check directly to the optimized
|
||||
todo_list.txt:109:PerfectNumbers.Core\Cpu\CpuConstants.cs:9: // TODO: Generate these masks at startup from the benchmarked Mod10 automaton tables so CPU filtering stays aligned with
|
||||
todo_list.txt:110:PerfectNumbers.Core\Cpu\CpuConstants.cs:11: // TODO: Fold in the Mod6ComparisonBenchmarks stride table here so callers can merge the mod 6 skips with these masks and
|
||||
todo_list.txt:111:PerfectNumbers.Core\ExponentRemainderStepper.cs:47: // TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
||||
todo_list.txt:112:PerfectNumbers.Core\ExponentRemainderStepper.cs:57: // TODO: Once divisor cycle lengths are mandatory, pull the delta multiplier from the
|
||||
todo_list.txt:113:PerfectNumbers.Core\ExponentRemainderStepper.cs:60: // TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
||||
todo_list.txt:114:PerfectNumbers.Core\ExponentRemainderStepper.cs:78: // TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
||||
todo_list.txt:115:PerfectNumbers.Core\ExponentRemainderStepper.cs:87: // TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle
|
||||
todo_list.txt:116:PerfectNumbers.Core\ExponentRemainderStepper.cs:89: // TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
||||
todo_list.txt:117:PerfectNumbers.Core\ExponentRemainderStepper.cs:124: // TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
||||
todo_list.txt:118:PerfectNumbers.Core\ExponentRemainderStepper.cs:133: // TODO: Once the divisor-cycle cache exposes a direct Montgomery delta, multiply it here instead
|
||||
todo_list.txt:119:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:21: set => _useDivisorCycles = value; // TODO: Delete the toggle once divisor cycle data is always consulted so CPU scans never fall back to the slower pure-Montgomery path.
|
||||
todo_list.txt:120:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:194: // TODO: Hoist MontgomeryDivisorData acquisition into the divisor-cycle cache so we reuse the
|
||||
todo_list.txt:121:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:203: // TODO: Swap this block lease for the direct single-cycle lookup once the cache exposes it
|
||||
todo_list.txt:122:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207: // TODO: When divisorCycle is zero (cache miss), compute only that single cycle on the device
|
||||
todo_list.txt:123:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
||||
todo_list.txt:124:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||
todo_list.txt:125:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||
todo_list.txt:127:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
||||
todo_list.txt:128:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||
todo_list.txt:129:PerfectNumbers.Core\IMersenneNumberDivisorByDivisorTester.cs:7: bool UseDivisorCycles { get; set; } // TODO: Remove the setter once divisor cycle usage becomes mandatory so all implementations always leverage the cached cycles.
|
||||
todo_list.txt:130:PerfectNumbers.Core\Cpu\MersenneNumberLucasLehmerCpuTester.cs:11: // TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering
|
||||
todo_list.txt:131:PerfectNumbers.Core\Cpu\MersenneNumberLucasLehmerCpuTester.cs:34: // TODO: Port this Lucas–Lehmer powmod to the ProcessEightBitWindows helper so the CPU
|
||||
todo_list.txt:132:PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:15: // TODO: Wire this residue scan into DivisorCycleCache so the mandatory cycle acceleration from the
|
||||
todo_list.txt:133:PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:17: // TODO: When a required cycle is missing from the snapshot, compute only that single cycle on the device
|
||||
todo_list.txt:134:PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:91: // TODO: Once cycle data is exposed for residue scans, consult the cached divisor cycle here
|
||||
todo_list.txt:135:PerfectNumbers.Core\InputParser.cs:13: // TODO: Replace Split/Parse with the span-based exponent parser so BigInteger inputs avoid allocations
|
||||
todo_list.txt:136:PerfectNumbers.Core\InputParser.cs:20: // TODO: Replace BigInteger.Parse(string) with the span overload once callers provide ReadOnlySpan<char> so
|
||||
todo_list.txt:137:PerfectNumbers.Core\InputParser.cs:27: // TODO: Swap decimal.Parse for the Utf8Parser-based decimal reader so callers avoid culture-aware conversions
|
||||
todo_list.txt:138:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:30: // TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block
|
||||
todo_list.txt:139:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:43: // TODO: Swap these Mod3/Mod5 zero checks to the cached residue tables once the automaton exposes
|
||||
todo_list.txt:140:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:53: // TODO: Swap this PowModWithCycle call for the ProcessEightBitWindows helper once
|
||||
todo_list.txt:141:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:65: // TODO: Route the phi-based powmods through the upcoming windowed helper to
|
||||
todo_list.txt:142:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:70: // TODO: Once the windowed pow2 helper is available expose
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:17: // TODO: When this lookup misses the snapshot, invoke the configured device to compute the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:38: // TODO: Point this CPU pow2mod check at the ProcessEightBitWindows helper once it
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:52: // TODO: Switch these phi-based powmods to the shared windowed helper so CPU
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:56: // TODO: Reuse the windowed pow2 helper for halfPow as soon as it is
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:63: // TODO: Replace this divisor powmod with the ProcessEightBitWindows
|
||||
PerfectNumbers.Core\Gpu\GpuContextPool.cs:10: // TODO: Introduce accelerator-specific warmup so pooled contexts precompile the ProcessEightBitWindows kernels and load
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:115: // TODO: Replace this naive doubling fallback with the unrolled-hex generator from
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:118: // TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:133: // TODO: Port this UInt128 path to the unrolled-hex cycle calculator so wide
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:205: // TODO: Replace this modulo-based filter with the cached Mod3/Mod5/Mod7/Mod11
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:213: // TODO: Migrate this generation path to the shared
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:287: // TODO: Swap these modulo checks for the cached Mod3/Mod5/Mod7/Mod11 helpers so
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:337: // TODO: Replace this modulo sieve with the cached Mod helpers once the divisor-cycle cache
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:459: // TODO: Switch this scalar fallback to the unrolled-hex stepping sequence once
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:462: // TODO: Expose a GPU-first branch here so high divisors leverage the ProcessEightBitWindows
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:194: // TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:202: // TODO: Replace these `%` computations with the precomputed Mod3/Mod5 tables so GPU kernels reuse cached residues
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:221: // TODO: Swap these `%` filters to the shared Mod helpers so the residue automaton matches the benchmarked
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:271: // TODO: Replace these Pow2Mod calls with the ProcessEightBitWindows helper when the shared windowed
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:308: // TODO: Once the ProcessEightBitWindows helper is available, switch this order kernel to that faster
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:420: // TODO: Swap Pow2Minus1Mod for the eight-bit window helper once the scalar version switches;
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:576: // TODO: Upgrade this pow2mod order kernel to the ProcessEightBitWindows helper once available so GPU residue
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:654: // TODO: Replace this Pow2Mod check with the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:697: private static readonly ConcurrentDictionary<Accelerator, KernelContainer> KernelCache = new(); // TODO: Replace this concurrent map with a simple accelerator-indexed lookup once kernel launchers are prewarmed during startup so we can drop the thread-safe wrapper entirely.
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:715: lock (kernels) // TODO: Remove this lock by pre-uploading the immutable small-cycle snapshot during initialization; once no mutation happens at runtime, the pool must expose a simple reference without synchronization.
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:722: var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot.
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:741: lock (kernels) // TODO: Inline these small-prime uploads into startup initialization alongside the small-cycle snapshot so we can drop runtime locking and keep the GPU pool free of synchronization.
|
||||
PerfectNumbers.Core\Gpu\GpuPrimeWorkLimiter.cs:11: // TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
|
||||
PerfectNumbers.Core\Gpu\GpuPrimeWorkLimiter.cs:32: // TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:259: // TODO: Replace this single-bit ladder with the ProcessEightBitWindows strategy measured
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:293: // TODO: Mirror the eight-bit window batching once the scalar helper above switches; the
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:380: // TODO: Pre-reduce the operands via the Montgomery ladder used in MulMod64Benchmarks so the GPU
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:398: // TODO: Fold these operands with the ImmediateModulo helper once the GPU shim exposes it, avoiding
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:539: // TODO: Replace this scalar binary GCD with the branchless reduction from
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:698: // TODO(MOD-OPT): Replace this bitwise long-division style reduction
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:709: // TODO(MOD-OPT): Plumb constants through caches in NttGpuMath.SquareCacheEntry
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:735: // TODO: Relocate this limb-based reducer to the benchmark project once the production
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:827: // TODO: Drop this native-modulo path from production after migrating callers to MulMod,
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:864: // TODO: Can we modify these loops to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:912: // TODO: Collapse this eight-step shift ladder into the ProcessEightBitWindows helper once it lands so
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:952: // TODO: Retire this struct-based Montgomery path from production after adopting the extension
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:972: // TODO: Same as above—migrate callers to the scalar extension to avoid this 6-7× slowdown.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1000: // TODO: This should operate on the instance itself, not on a copy. Avoid creating new instances anywhere.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1003: // TODO: Can we modify these loops to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1050: // TODO: Swap this copy-heavy path for the pooled base/exponent ladder used in
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1056: // TODO: Replace the single-bit square-and-multiply loop with the 64-bit windowed
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1076: // TODO: Share the pooled ladder state from GpuUInt128MulModBenchmarks here as well so
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1081: // TODO: Upgrade this loop to the same 64-bit windowed ladder proven fastest in
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1101: // TODO: Replace this Fermat inversion with the Montgomery ladder highlighted in
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1128: // TODO: Reuse variables to reduce register pressure following the fused-limb layout
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1191: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1194: // TODO: Since we ignore the first result element, can we create a version of the function which calculates and returns only the second element?
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1198: // TODO: Why not just modify left instance directly instead using out parameters?
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1205: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1226: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1272: // TODO: Can we identify where we deal with Mersenne exponent in EvenPerfectBitScanner and directly use MulModMersenne there, removing this branch?
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1282: // TODO: Can we modify this loop to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1309: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1330: // TODO: Can we modify this loop to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1362: // TODO: Can we modify this loop to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1406: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1434: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1463: // TODO: Check if the TODO below is still relevant.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1465: // TODO(MOD-OPT): Montgomery/Barrett integration plan
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:39: // TODO: Swap this ConcurrentDictionary for the pooled dictionary variant highlighted in
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:51: // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags.
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:74: // TODO: Replace these repeated Mod10/Mod8/Mod3/Mod5 calls with a residue automaton walk so
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:99: // TODO: Swap this fallback to the shared windowed order helper once CalculateOrder migrates
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:117: // TODO: Reuse the same residue-automaton fast path here so the GPU warmup staging avoids `%` and
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:189: // TODO: Replace this CalculateOrder call with the upcoming windowed helper so
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:219: // TODO: Replace this `% 3` check with ULongExtensions.Mod3 to align the early rejection with
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:328: // TODO: Swap this legacy kernel over to the ProcessEightBitWindows helper so GPU order scans share the
|
||||
PerfectNumbers.Core\Gpu\GpuWorkLimiter.cs:11: // TODO: Replace the per-call Releaser allocation with the pooled struct-based guard from the
|
||||
PerfectNumbers.Core\Gpu\GpuWorkLimiter.cs:36: // TODO: Consolidate with GpuPrimeWorkLimiter so both limiters share a pooled SemaphoreSlim and avoid rebuilding the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:39: // TODO: Replace this `%` with the benchmarked Math.DivRem fast path so the hot filter avoids
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:50: // TODO: Replace this dictionary with the divisor-cycle order cache once the benchmarks confirm the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:56: // TODO: Collapse this wrapper once all callers promote operands to UInt128 so we
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:62: // TODO: Inline this shim once callers can invoke OrderOf2ModPrime directly; the extra
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:71: // TODO: Replace this lock with the lock-free order cache once the divisor-cycle snapshot exposes
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:84: // TODO: Pull these factor arrays from ArrayPool once the factoring helpers adopt the pooled
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:98: // TODO: Swap this `%`/`/` loop for the Math.DivRem-based reducer highlighted in the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:119: // TODO: Integrate divisor-cycle data here so repeated order refinement uses cached cycle
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:134: // TODO: Replace this duplicate powmod with ULongExtensions.ModPow64 so we inherit the optimized MulMod64 path that led the MulMod64Benchmarks on large operands.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:166: private static ulong MulMod64(ulong a, ulong b, ulong mod) => (ulong)((UInt128)a * b % mod); // TODO: Route this through ULongExtensions.MulMod64 so the powmod above adopts the inline UInt128 implementation that dominated the MulMod64Benchmarks.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:169: // TODO: Switch to UInt128Extensions.ModPow once its MulMod backend adopts the faster UInt128BuiltIn path highlighted in MulHighBenchmarks to avoid the BigInteger fallback.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:203: BigInteger product = (BigInteger)a * b; // TODO: Replace this BigInteger reduction with the forthcoming UInt128 intrinsic helper measured faster in Mul64Benchmarks and MulHighBenchmarks for huge operands.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:256: var queue = new Queue<ulong>(); // TODO: Replace this Queue with the pooled stack from the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:296: // TODO: Rent the result array from ArrayPool once the factoring pipeline consumes spans so we
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:315: var queue = new Queue<UInt128>(); // TODO: Replace this with the pooled UInt128 stack once the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:354: var result = new UInt128[dict.Count]; // TODO: Rent this array from ArrayPool<UInt128> so
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:374: var rng = new Random(1234567); // TODO: Replace this RNG with the deterministic span-based
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:398: x = (MulMod64(x, x, n) + c) % n; // TODO: Swap the `% n` with the Montgomery folding
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:406: y = (MulMod64(y, y, n) + c) % n; // TODO: Use the same Montgomery folding helper here
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:414: y = (MulMod64(y, y, n) + c) % n; // TODO: Replace this modulo with the optimized helper
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:443: t = a % b; // TODO: Replace this with the binary GCD helper used in the gcd benchmarks so we
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:452: // TODO: Port this Miller–Rabin routine to UInt128 intrinsics so we avoid the BigInteger.ModPow calls that lag by orders of magnitude on the large inputs highlighted in the Pow2MontgomeryMod benchmarks.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:526: var rng = new Random(1234567); // TODO: Replace with the deterministic UInt128 sequence from
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:549: x = (MulMod128(x, x, n) + c) % n; // TODO: Swap this modulo for the UInt128 Montgomery
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:556: y = (MulMod128(y, y, n) + c) % n; // TODO: Use the same optimized reducer for the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:563: y = (MulMod128(y, y, n) + c) % n; // TODO: Replace with the optimized reducer so both
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:571: d = Gcd128(diff, n, ct); // TODO: Move this to the binary GCD implementation once the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:593: t = a % b; // TODO: Replace with the binary GCD helper once the UInt128 benchmarks land so
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:20: // TODO: Replace these `%` reductions with the shared ProcessEightBitWindows helper so initialization benefits from the
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:26: // TODO: Inline this accessor into callers so the hot residue path reads the backing fields directly
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:42: // TODO: Replace this repeated doubling with the shared ProcessEightBitWindows helper (or cycle-aware lookup)
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:45: // TODO: Once divisor cycles power the residue stepper, prefer stepping by cycle lengths instead of incrementing
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:57: // TODO: Once the scalar windowed pow2 helper lands, reroute this `%` path through it to avoid the slower
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:64: // TODO: Switch to a cached divisor-cycle remainder instead of computing a fresh modular inverse for every
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:71: // TODO: Use the shared pow2mod remainder cache here so this `%` becomes a subtraction-based fold instead of the
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:80: // TODO: Collapse this wrapper once callers can invoke Step directly; the extra indirection shows up
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:102: // TODO: Replace this division-heavy Euclidean loop with the binary modular inverse that reuses divisor
|
||||
PerfectNumbers.Core\KRangeFinder.cs:13: // TODO: Pull alphaP from AlphaCache (or a pooled rational builder) so this loop reuses the
|
||||
PerfectNumbers.Core\KRangeFinder.cs:16: // TODO: Switch this multiply to the pooled ERational span helpers identified in the scanner's
|
||||
PerfectNumbers.Core\KRangeFinder.cs:41: // TODO: Collapse this wrapper once the callers can consume the tuple directly; the extra call adds
|
||||
PerfectNumbers.Core\KRangeFinder.cs:53: // TODO: Replace Parallel.For with the shared low-overhead scheduler highlighted in the divisor-cycle
|
||||
PerfectNumbers.Core\KRangeFinder.cs:83: // TODO: Remove this pass-through once the scanning CLI switches to tuple-returning APIs; flattening
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:189: // TODO: Replace this subtraction loop with the divisor-cycle stepping helper so identity
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:206: // TODO: Replace this PowMod128 call with the upcoming eight-bit window helper once the
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:252: // TODO: Swap this modulo fallback for the shared divisor-cycle remainder helper so 64-bit
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:257: // TODO: Use the Montgomery folding helper measured faster in the Pow2Montgomery benchmarks so this
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:263: // TODO: Consult MersenneDivisorCycles.Shared (or similar caches) here so we reuse precomputed cycle lengths
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:295: // TODO: Route this through the planned UInt128 windowed powmod so the inner MulMod adopts the
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:314: // TODO: Replace this double-and-add fallback with the UInt128 intrinsic-backed multiplier once the
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:49: set => _useDivisorCycles = value; // TODO: Remove this flag once GPU runs unconditionally fetch cycle lengths so every divisor check benefits from the measured speedups.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:298: // TODO: Stage MontgomeryDivisorData in a GPU-ready cache keyed by divisor so we can copy
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:372: // TODO: Replace the block lease with a direct single-cycle lookup so the GPU path
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:395: // TODO: Drop this block hand-off once the cache exposes a direct
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:409: // TODO: Replace this `%` with the cycle-cache helper that subtracts precomputed
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:425: // TODO: Trigger an immediate GPU computation for the single missing cycle without storing
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:476: // TODO: Preload ProcessEightBitWindows-friendly divisor metadata for GPU batches so this
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:563: hits[index] = exponent.Pow2MontgomeryMod(divisor) == 1UL ? (byte)1 : (byte)0; // TODO: Replace Pow2MontgomeryMod with the ProcessEightBitWindows helper once available so GPU by-divisor scans reuse the benchmarked fast pow2 ladder.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:632: // TODO: Replace this guard with an on-demand cycle computation that runs on the device selected
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:787: // TODO: Replace this `%` with the divisor-cycle remainder helper so we reuse cached
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:858: results[index] = exponent.Pow2MontgomeryModMontgomery(divisor); // TODO: Swap this Montgomery-domain pow2 with the windowed helper slated to replace the slower ladder in Pow2Minus1Mod benchmarks.
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:43: // TODO: Swap this per-run enumerator creation for the shared PrimeIterator so by-divisor scans reuse the
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:219: // TODO: Swap Task.Factory.StartNew for the shared low-overhead work queue once the scanner's
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:240: bool useDivisorCycles = tester.UseDivisorCycles; // TODO: Remove the conditional path once all testers always enable divisor cycles to keep every divisor scan on the faster cached-length track.
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:262: // TODO: Remove this block leasing once the cache exposes a direct
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:266: // TODO: When divisorCycle returns zero, compute only that single cycle on the
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:421: // TODO: Replace this odd-only linear allocator with the divisor-cycle aware stride planner once the cache exposes
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:498: // TODO: Integrate divisor-cycle skip tables here so the local allocator leaps across cached composite
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:521: // TODO: Replace these `%` filters with Mod3/Mod5/Mod7/Mod11 helpers so candidate sieving reuses the
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:78: // TODO: Replace this `%` check with the shared divisor-cycle divisibility helper so GPU scans
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:114: // TODO: When this lookup falls outside the preloaded snapshot, invoke the single-cycle GPU helper
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:117: // TODO: Feed this check through the ProcessEightBitWindows-based residue tracker so we avoid the slow
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:150: // TODO: Swap Pow2Mod for the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:169: // TODO: Replace this trailing Pow2Mod call with the ProcessEightBitWindows variant once the shared
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberIncrementalGpuTester.cs:23: var pow2Kernel = gpuLease.Pow2ModKernel; // TODO: Route this binding to the ProcessEightBitWindows kernel once the scalar helper lands so GPU incremental scans inherit the windowed speedup from GpuPow2ModBenchmarks.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberIncrementalGpuTester.cs:49: // TODO: Swap this multiply-and-add initialization for the divisor-cycle stepping helper so the GPU incremental path reuses cached offsets instead of recomputing twoP multiples every batch.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberIncrementalGpuTester.cs:82: // TODO: Replace this per-lane multiplication and the subsequent q += twoP stride with the shared divisor-cycle ladder so each iteration advances by cached cycle increments and automatically triggers on-demand GPU computations when the snapshot lacks the requested cycle.
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:45: // TODO: Remove this wrapper and expose the field directly once callers switch to struct-based residue
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:78: // TODO: Once divisor cycle lookups feed residue scanning, reuse the cached cycle length here to skip directly to the
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:121: // TODO: Replace the `% 10` computation with the Mod10 helper once Ending7Automaton plugs into the
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:126: // TODO: Precompute the initial residues via the divisor-cycle cache so this setup phase reuses the
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:133: // TODO: Swap this `%` check for the binary-gcd aware reducer once the automaton wiring shares the
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:140: // TODO: Inline this getter into the automaton consumers so residue scans can read the backing field
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:155: // TODO: Pre-register every modulus routed through the automaton so the fallback `%` path disappears;
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:183: // TODO: Once divisor cycles are mandatory, reuse the cached cycle offset per modulus to skip directly to valid
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:201: // TODO: Switch this to the binary GCD helper used in GpuUInt128BinaryGcdBenchmarks so we avoid `%`
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:46: // TODO: Inline this wrapper once callers request IsPrime directly so the Lucas–Lehmer fast path
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:57: // TODO: Replace this `% 3` guard with ULongExtensions.Mod3 once GPU LL filtering reuses the benchmarked bitmask helper.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:85: var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:116: var kernel = GetBatchKernel(accelerator); // TODO: Switch this batch path to the ProcessEightBitWindows residue kernel once Lucas–Lehmer integrates the benchmarked windowed pow2 helper for small exponents.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:126: // TODO: Replace this per-exponent shift with a small shared table (one entry per supported exponent < 128)
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:190: // TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:300: // TODO: Replace int.TryParse with the span-based Utf8Parser helper when loading cached parameters so we avoid
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:307: // TODO: Switch these ulong.Parse calls to the Utf8Parser-based fast-path once we expose a zero-allocation reader for
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:327: // TODO: Replace StreamWriter with the pooled TextFileWriter pipeline so persisting NTT
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:344: // TODO: Move this Parallel.For to the shared low-overhead work scheduler once the NTT parameter
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:395: // TODO: Reuse the divisor-cycle cache to factor phi via the precomputed small-prime windows once
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:424: // TODO: Replace these `%` factor checks with the shared Mod helpers (Mod3/Mod5/etc.) once the GPU
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:458: // TODO: Swap this `%` for the divisor-cycle aware Mod helper once the residue pre-checks expose it
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:476: // TODO: Route these powmods through the ProcessEightBitWindows helper once it lands for GPU
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:490: // TODO: Swap this UInt128 `%` reduction for the GPU-compatible MulMod helper once it adopts the faster
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:22: private static readonly ConcurrentDictionary<ulong, CacheEntry> Cache = new(); // TODO: Replace this with a plain Dictionary that never mutates after startup once the divisor-cycle snapshot becomes read-only so Montgomery lookups avoid any concurrent collections.
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:23: // TODO: Delete the block-membership tracking entirely when the divisor-cycle cache stops producing
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:36: // TODO: Short-circuit block indices greater than zero once the cache stops prefetching future
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:44: // TODO: Remove this entire release hook once the cycle cache no longer hands out dynamic blocks;
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:97: // TODO: Collapse this retry loop once the cache becomes a simple snapshot dictionary. With the
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:104: // TODO: Delete this method once block membership goes away; the single snapshot model must avoid
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:122: // TODO: Return 0 here once the divisor-cycle cache no longer tracks additional blocks so
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:138: // TODO: Replace this generic `%` reduction with the UInt128 Montgomery folding helper measured faster in
|
||||
PerfectNumbers.Core\PerfectNumberConstants.cs:10: // TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
||||
PerfectNumbers.Core\PerfectNumberConstants.cs:12: // TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberOrderGpuTester.cs:25: }; // TODO: Migrate the Pow2Mod branch to the ProcessEightBitWindows order kernel once the shared helper replaces the single-bit ladder so GPU order scans match benchmark wins.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberOrderGpuTester.cs:26: // TODO: Inline the IncrementalOrderKernel call once the ProcessEightBitWindows helper lands so this wrapper stops forwarding directly to the kernel and the hot path loses one indirection.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberOrderGpuTester.cs:28: var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberOrderGpuTester.cs:42: // TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:11: // TODO: Integrate MersenneDivisorCycles.Shared to consult cycle lengths for small q (<= 4M) and fast-reject
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:14: // TODO: When cycles are missing for larger q values, compute only the single required cycle on the device
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:30: var kernel = gpuLease.Pow2ModKernel; // TODO: Swap this to the ProcessEightBitWindows kernel once GpuUInt128.Pow2Minus1Mod adopts the shared windowed helper measured fastest in GpuPow2ModBenchmarks.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:48: // TODO: Replace the direct UInt128 multiply/add updates below with the residue-cycle stepping helper so that q advances reuse cached cycle increments rather than recomputing twoP multiples on every iteration.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:98: q += twoP * processed; // TODO: Swap this multiplication for the shared cycle stepping helper once residue cycles are mandatory so we can reuse the cached remainder ladder instead of performing UInt128 multiply operations.
|
||||
PerfectNumbers.Core\PowerCache.cs:16: // TODO: Rent this buffer from ArrayPool<BigInteger> (or a dedicated pooled cache) so expanding
|
||||
PerfectNumbers.Core\PowerCache.cs:27: // TODO: Replace Array.Resize with a pooled copy so we reuse previously rented buffers instead
|
||||
PerfectNumbers.Core\PowerCache.cs:42: // TODO: Swap this BigInteger chain with the UInt128-based Montgomery ladder from
|
||||
PerfectNumbers.Core\PowerCache.cs:76: // TODO: Move the high-precision branch to the benchmark project once production
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:28: // TODO(NTT-OPT): Consider passing backend explicitly to avoid global state.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:32: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128>> MulKernelCache = new(); // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:33: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128>> StageKernelCache = new(); // TODO: Same as above – materialize staged kernels during startup so we can drop ConcurrentDictionary usage entirely per the benchmark guidance.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:34: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128>> ScaleKernelCache = new(); // TODO: Promote to the static kernel table initialized alongside the GpuModularArithmeticBenchmarks fast path.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:35: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong>> StageMontKernelCache = new(); // TODO: Inline the Montgomery kernels into the startup table instead of using a concurrent cache now that we no longer mutate state at runtime.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:36: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>> StageBarrett128KernelCache = new(); // TODO: Same plan – reuse the precomputed kernels measured fastest in MontgomeryMultiplyBenchmarks rather than looking them up dynamically.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:37: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong>> ScaleBarrett128KernelCache = new(); // TODO: Collapse into the startup kernel array once Barrett128 constants are preloaded.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:38: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>> SquareBarrett128KernelCache = new(); // TODO: Fold these kernels into the same startup table to avoid concurrent access overhead highlighted in GpuModularArithmeticBenchmarks.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:39: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>> ToMont64KernelCache = new(); // TODO: Prebind and reuse the Montgomery conversions from MontgomeryMultiplyBenchmarks instead of storing them in a concurrent cache.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:40: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong>> FromMont64KernelCache = new(); // TODO: As above – drop ConcurrentDictionary once startup prewarms every kernel.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:41: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong>> SquareMont64KernelCache = new(); // TODO: Inline into the static kernel table established during initialization.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:42: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>> ScaleMont64KernelCache = new(); // TODO: Move to the startup kernel table so Montgomery-scaled launches skip concurrent lookups.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:44: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128>> ForwardKernelCache = new(); // TODO: Warm these forward kernels during initialization and store them in a plain array so the LucasLehmerGpuBenchmarks launch costs disappear.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:46: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128>> InverseKernelCache = new(); // TODO: Same as above – replace with startup-prepared tables per LucasLehmerGpuBenchmarks guidance.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:48: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int>> BitReverseKernelCache = new(); // TODO: Precompute bit-reversal kernels during boot so we can remove the concurrent lookup overhead flagged in the benchmarks.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:60: // TODO(MOD-OPT): Precompute and store Montgomery/Barrett constants for modulus here
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:114: // TODO: Pull these stage roots from the precomputed tables measured fastest in
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:125: // TODO: Reuse the same precomputed tables for inverse roots so we do not repeat the
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:148: // TODO: Preload MontNPrime64/MontR values using the MontgomeryMultiplyBenchmarks helper so
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:235: // TODO(NTT-OPT): Add a TwiddleCache for stage-wise Cooley–Tukey NTT.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:333: // TODO(NTT-OPT): Clear TwiddleCache once introduced.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:356: // TODO(NTT-OPT): Remove per-accelerator twiddle buffers once added.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:398: // TODO(NTT-OPT): Introduce stage-wise Cooley–Tukey kernels and a
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:407: // TODO(NTT-OPT): Mirror the forward stage-wise design for the inverse
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:420: // TODO(NTT-OPT): Consider shared-memory tiling to improve locality:
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:424: // TODO: Replace this `%` with a bitmask when `half` is a power of two so the stage index math matches the faster
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:500: // TODO: Use the bitmask-based remainder helper here as well to remove `%` from the butterfly stage and align with the
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:547: // TODO(MOD-OPT): Stage kernel using 64-bit Montgomery multiplication. Requires data and twiddles in Montgomery domain.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:551: // TODO: Apply the bitmask remainder helper to this stage too so every butterfly path drops the slower `%` operation.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:752: // TODO(NTT-OPT): Replace this reference O(n^2) kernel with a stage-wise
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:772: // TODO(NTT-OPT): Replace this O(n^2) inverse with stage-wise butterflies
|
||||
PerfectNumbers.Core\PrimeCache.cs:26: // TODO: Swap the Open.Numeric enumerator for the staged sieve batches we benchmarked; the
|
||||
PerfectNumbers.Core\PrimeCache.cs:28: // TODO: Fold in the Mod6 stride planner that topped Mod6ComparisonBenchmarks so the CPU cache
|
||||
PerfectNumbers.Core\PrimeCache.cs:93: // TODO: Move this incremental append to the shared sieve batches so we amortize the
|
||||
PerfectNumbers.Core\PrimeCache.cs:96: // TODO: Apply the Mod6 stride scheduler here as well so on-demand extensions mirror the
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:55: // TODO: Reuse a single squared local for `p` within this loop so we avoid recalculating
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:63: // TODO: Replace this trial-division `%` with the sieve-based generator that avoids
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:78: // TODO: Reuse a single cached `candidateSquared` per iteration so we avoid issuing
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:104: // TODO: Switch this increment to the Mod6 stepping table identified in the Mod6ComparisonBenchmarks
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:113: // TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:125: // TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster
|
||||
PerfectNumbers.Core\PrimeTester.cs:14: // TODO: Eliminate this wrapper once callers can reach IsPrimeInternal directly; the extra
|
||||
PerfectNumbers.Core\PrimeTester.cs:28: // TODO: Inline the single-value GPU sieve fast path from GpuModularArithmeticBenchmarks so this wrapper
|
||||
PerfectNumbers.Core\PrimeTester.cs:60: // TODO: Replace this modulo check with ULongExtensions.Mod5 so the CPU hot path
|
||||
PerfectNumbers.Core\PrimeTester.cs:82: // TODO: Route this small-prime filtering through the shared divisor-cycle cache once
|
||||
PerfectNumbers.Core\PrimeTester.cs:121: // TODO: Replace this ad-hoc ArrayPool buffer with the pinned span cache from
|
||||
PerfectNumbers.Core\PrimeTester.cs:164: // TODO: Replace this ConcurrentBag with the lock-free ring buffer variant validated in
|
||||
PerfectNumbers.Core\PrimeTester.cs:237: // TODO: Prewarm this per-accelerator cache during startup (and reuse a simple array keyed by accelerator index)
|
||||
PerfectNumbers.Core\PrimeTester.cs:281: // TODO: Replace the `% 5` branch with the GPU Mod5 helper once the sieve kernel can
|
||||
PerfectNumbers.Core\PrimeTester.cs:309: // TODO: Route this modulo through the shared divisor-cycle cache once exposed to GPU kernels
|
||||
PerfectNumbers.Core\PrimeTester.cs:322: // TODO: Replace this on-the-fly GCD probe with the cached factor table derived from
|
||||
PerfectNumbers.Core\PrimeTester.cs:331: // TODO: Route this batch helper through the shared GPU kernel pool from
|
||||
PerfectNumbers.Core\PrimeTester.cs:357: // TODO: Swap this handwritten binary GCD for the optimized helper measured in
|
||||
PerfectNumbers.Core\PrimeTester.cs:398: // TODO: Replace this inline GPU binary GCD with the kernel extracted from
|
||||
PerfectNumbers.Core.Tests\TextFileWriterTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
PerfectNumbers.Core\RationalHelper.cs:13: // TODO: Replace the BigInteger-based GCD with the UInt128-friendly binary GCD helper so this rounding path avoids
|
||||
PerfectNumbers.Core\RationalHelper.cs:20: // TODO: Cache a reusable byte buffer here so converting to EInteger no longer allocates per call when parsing alpha
|
||||
PerfectNumbers.Core\RationalHelper.cs:28: // TODO: Inline this floor conversion with the span-based ladder that dominated
|
||||
PerfectNumbers.Core\RationalHelper.cs:39: // TODO: Replace this ceiling branch with the branchless adjustment measured fastest
|
||||
PerfectNumbers.Core\RationalNumbers.cs:8: // TODO: Precompute additional frequently used rationals (e.g., OneHalf, ThreeHalves) so hot alpha computations stop
|
||||
PerfectNumbers.Core.Tests\RleBlacklistTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
PerfectNumbers.Core\RleBlacklist.cs:31: // TODO: Replace this HashSet with the pooled ValueHashSet from the blacklist benchmarks so repeated
|
||||
PerfectNumbers.Core\RleBlacklist.cs:144: // TODO: Cache BuildRleKey results per divisor cycle bucket so residue scans reuse the
|
||||
PerfectNumbers.Core\RleBlacklist.cs:154: // TODO: Replace this per-bit loop with the lookup-table driven builder validated in the
|
||||
PerfectNumbers.Core\RleBlacklist.cs:247: // TODO: Replace int.TryParse with the Utf8Parser-based span helper so blacklist normalization avoids
|
||||
PerfectNumbers.Core\RleBlacklist.cs:330: // TODO: Swap this int.TryParse for the upcoming Utf8Parser fast path so nested patterns reuse the
|
||||
PerfectNumbers.Core\StringBuilderPool.cs:12: // TODO: Ensure oversized builders remain eligible for reuse without trimming so call sites keep their full capacity.
|
||||
PerfectNumbers.Core\StringBuilderPool.cs:19: // TODO: Preserve the builder's capacity when returning it so the pool hands the same buffer back without shrinkage.
|
||||
EvenPerfectBitScanner.Benchmarks\MulHighBenchmarks.cs:40: // TODO: Replace ULongExtensions.MulHigh with the UInt128-based helper on CPU paths; it was
|
||||
PerfectNumbers.Core\TextFileWriter.cs:28: // TODO: Surface a lock-free position snapshot (for example via Interlocked.Read on a cached long)
|
||||
PerfectNumbers.Core\TextFileWriter.cs:48: // TODO: Use the pooled writer buffers planned for the TextFileWriter fast path so truncation flushes
|
||||
PerfectNumbers.Core\TextFileWriter.cs:65: // TODO: Buffer these writes through an ArrayPool-backed accumulator so we only flush when the
|
||||
PerfectNumbers.Core\TextFileWriter.cs:80: // TODO: Buffer binary writes with the same ArrayPool-backed accumulator planned for text so GPU result
|
||||
PerfectNumbers.Core\TextFileWriter.cs:89: // TODO: Return the writers to a pooled wrapper once the buffered pipeline lands so disposing the
|
||||
EvenPerfectBitScanner.Benchmarks\MulMod64Benchmarks.cs:172: // TODO: Callers in production should migrate to ULongExtensions.MulMod64 where GPU parity
|
||||
EvenPerfectBitScanner.Benchmarks\MulMod64Benchmarks.cs:188: // TODO: Retire the deferred GPU shim from runtime paths and keep it only for benchmarks;
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:11: // TODO: Replace this hand-rolled binary GCD with the shared subtract-free ladder from
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:63: // TODO: If the cache lacks this cycle, immediately schedule the configured device
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:79: // TODO: Switch this divisor-order powmod to the ProcessEightBitWindows helper so the
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:123: // TODO: Replace this direct `%` test with the shared divisor-cycle filter once the
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:138: // TODO: Fold these reductions into the multiply-high trick captured in Mod3BenchmarkResults so
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:147: // TODO: Replace the `% 5` operations with the precomputed multiply-high constants from the Mod5
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:167: // TODO: Inline the lookup-based Mod7 reducer validated in the residue benchmarks so this helper
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:191: // TODO: Precompute the 2^64 ≡ 6 folding constants once so this path stops recomputing `% 10`
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:214: // TODO: Collapse this Mod10 switch into the shared lookup table from the CLI benchmarks so we
|
||||
PerfectNumbers.Core\UInt128Numbers.cs:9: // TODO: Populate this table from the UInt128 residue benchmarks so GPU kernels can pull precomputed constants
|
||||
PerfectNumbers.Core\UInt128Numbers.cs:11: // TODO: Extend the table with cached Mod3/Mod5 folding constants so both CPU and GPU residue helpers can reuse
|
||||
PerfectNumbers.Core\UIntExtensions.cs:9: // TODO: Only add Mod7/Mod11 lookup helpers once we have a variant that beats the `%` baseline (current prototypes lose per Mod7/Mod11 benchmarks).
|
||||
PerfectNumbers.Core\ULongExtensions.cs:23: // TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
|
||||
PerfectNumbers.Core\ULongExtensions.cs:36: // TODO: Replace this `%` driven factor peeling with the divisor-cycle aware
|
||||
PerfectNumbers.Core\ULongExtensions.cs:87: // TODO: Swap this modulo check for the shared small-prime cycle filter once the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:204: // TODO: Investigate replacing this manual decomposition with the UInt128-based implementation
|
||||
PerfectNumbers.Core\ULongExtensions.cs:238: // TODO: Replace this `%` with the Montgomery folding helper highlighted in MulMod64Benchmarks so the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:257: // TODO: Replace this fallback with the UInt128 Montgomery helper measured fastest in
|
||||
PerfectNumbers.Core\ULongExtensions.cs:264: // TODO: Remove this GPU-compatible shim from production once callers migrate to MulMod64,
|
||||
PerfectNumbers.Core\ULongExtensions.cs:273: // TODO: Move this deferred helper to the benchmark suite; the baseline MulMod64 avoids the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:313: // TODO: Switch to the ProcessEightBitWindows helper once the scalar implementation ships so CPU callers
|
||||
PerfectNumbers.Core\ULongExtensions.cs:347: // TODO: Route this Montgomery-domain variant through the same windowed pow2 helper once available so delta
|
||||
PerfectNumbers.Core\ULongExtensions.cs:372: // TODO: Swap this modulo with the divisor-cycle remainder helper from Pow2MontgomeryModCycleComputationBenchmarks so we
|
||||
PerfectNumbers.Core\ULongExtensions.cs:395: // TODO: Port this scalar PowMod fallback to the ProcessEightBitWindows helper so CPU callers get the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:447: // TODO: Wire this cycle-aware overload into the ProcessEightBitWindows helper so the reduced exponent path
|
||||
PerfectNumbers.Core\ULongExtensions.cs:481: // TODO: Replace this modulo with the cached cycle remainder produced by the divisor-cycle cache so PowModWithCycle avoids
|
||||
PerfectNumbers.Core\ULongExtensions.cs:502: // TODO: Replace this UInt128-cycle overload with the ProcessEightBitWindows helper so large-exponent CPU scans
|
||||
PerfectNumbers.Core\ULongExtensions.cs:534: // TODO: Swap this modulo with the upcoming UInt128 cycle remainder helper so large-exponent scans reuse cached
|
||||
PerfectNumbers.Core\ULongExtensions.cs:560: // TODO: Migrate this UInt128 exponent overload to ProcessEightBitWindows so the large-cycle reductions drop the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:592: // TODO: Swap this modulo with the shared UInt128 cycle remainder helper once available so CRT powmods reuse cached
|
||||
PerfectNumbers.Core\ULongExtensions.cs:639: // TODO: Replace this `% modulusCandidate` with the cached residue helper derived from Mod10_8_5_3Benchmarks so CRT
|
||||
PerfectNumbers.Core\ULongExtensions.cs:648: // TODO: Swap this final `% modulus` with the pooled remainder cache so the CRT result write-back avoids one more division,
|
||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user